From f296f0dd927f6ecd35543bfa5f0a504f11a6992d Mon Sep 17 00:00:00 2001 From: JoyChou Date: Fri, 10 Apr 2020 19:08:26 +0800 Subject: [PATCH 01/21] 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 02/21] 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 03/21] 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 @@ - +