m = new HashMap<>();
+ m.put("Username", username);
+
+ return JSON.toJSONString(m);
+ }
+
+ /**
+ * Set the response content-type to application/javascript.
+ *
+ * http://localhost:8080/jsonp/referer?callback=test
+ */
+ @RequestMapping(value = "/referer", produces = "application/javascript")
+ private String referer(HttpServletRequest request) {
+ String callback = request.getParameter("callback");
+ return callback + "(" + getUserInfo2JsonStr(request) + ")";
+ }
+
+ /**
+ * Direct access does not check Referer, non-direct access check referer.
+ * Developer like to do jsonp testing like this.
+ *
+ * http://localhost:8080/jsonp/emptyReferer?callback=test
+ */
+ @RequestMapping(value = "/emptyReferer", produces = "application/javascript")
+ private String emptyReferer(HttpServletRequest request) {
+ String referer = request.getHeader("referer");
+
+ if (null != referer && !SecurityUtil.checkURLbyEndsWith(referer, urlwhitelist)) {
+ return "error";
+ }
+
+ String callback = request.getParameter("callback");
+ return callback + "(" + getUserInfo2JsonStr(request) + ")";
+ }
+
+ /**
+ * Adding callback or cback on parameter can automatically return jsonp data.
+ * http://localhost:8080/jsonp/advice?callback=test
+ * http://localhost:8080/jsonp/advice?_callback=test
+ *
+ * @return Only return object, AbstractJsonpResponseBodyAdvice can be used successfully.
+ * Such as JSONOjbect or JavaBean. String type cannot be used.
+ */
+ @RequestMapping(value = "/advice", produces = MediaType.APPLICATION_JSON_VALUE)
+ public JSONObject advice(HttpServletRequest request) {
+ return JSON.parseObject(getUserInfo2JsonStr(request));
+ }
+
+
+ /**
+ * http://localhost:8080/jsonp/mappingJackson2JsonView?callback=test
+ * Reference: https://p0sec.net/index.php/archives/122/ from p0
+ * Affected version: java-sec-code test case version: 4.3.6
+ * - Spring Framework 5.0 to 5.0.6
+ * - Spring Framework 4.1 to 4.3.17
+ */
+ @RequestMapping(value = "/mappingJackson2JsonView", produces = MediaType.APPLICATION_JSON_VALUE)
+ 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", produces = "application/javascript")
+ private String safecode(HttpServletRequest request) {
+ String referer = request.getHeader("referer");
+
+ if (!SecurityUtil.checkURLbyEndsWith(referer, urlwhitelist)) {
+ return "error";
+ }
+
+ String callback = request.getParameter("callback");
+ return callback + "(" + getUserInfo2JsonStr(request) + ")";
+ }
+
+
+ /**
+ * http://localhost:8080/jsonp/getToken
+ *
+ * @return token {"token":"115329a7-3a85-4c31-9c02-02fa1bd1fdf8","parameterName":"_csrf","headerName":"X-XSRF-TOKEN"}
+ */
+ @RequestMapping("/getToken")
+ public CsrfToken csrf(CsrfToken token) {
+ return token;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java b/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java
new file mode 100644
index 00000000..5463a5a5
--- /dev/null
+++ b/src/main/java/org/joychou/controller/jsonp/JSONPAdvice.java
@@ -0,0 +1,18 @@
+package org.joychou.controller.jsonp;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;
+
+// AbstractJsonpResponseBodyAdvice will be removed as of Spring Framework 5.1, use CORS instead.
+// Since Spring Framework 4.1
+// Springboot 2.1.0 RELEASE use spring framework 5.1.2
+@ControllerAdvice
+public class JSONPAdvice extends AbstractJsonpResponseBodyAdvice {
+
+ // method of using @Value in constructor
+ public JSONPAdvice(@Value("${joychou.security.jsonp.callback}") String[] callback) {
+ super(callback); // Can set multiple paramNames
+ }
+
+}
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..6cefe27f
--- /dev/null
+++ b/src/main/java/org/joychou/controller/othervulns/ooxmlXXE.java
@@ -0,0 +1,79 @@
+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;
+
+import static org.apache.commons.lang.StringUtils.isBlank;
+
+/**
+ * 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();
+ String result = "";
+
+ 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) {
+ result += cell.getStringCellValue()+ " ";
+ } else if(cell.getCellType() == XSSFCell.CELL_TYPE_NUMERIC) {
+ result += cell.getNumericCellValue()+ " ";
+ } else {
+ logger.info("errors");
+ }
+ }
+ }
+ if ( isBlank(result) ){
+ result = "xxe test";
+ }
+
+ return result;
+ }
+}
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..4ca4bf2f
--- /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 {
+ Workbook wb = StreamingReader.builder().open(file.getInputStream());
+ }
+
+
+ public static void main(String[] args) throws Exception {
+ Workbook wb = 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..b9bc8341
--- /dev/null
+++ b/src/main/java/org/joychou/dao/User.java
@@ -0,0 +1,34 @@
+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/mapper/UserMapper.java b/src/main/java/org/joychou/mapper/UserMapper.java
new file mode 100644
index 00000000..962f465d
--- /dev/null
+++ b/src/main/java/org/joychou/mapper/UserMapper.java
@@ -0,0 +1,29 @@
+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 findByUserNameVul(@Param("username") String username);
+
+ List findByUserNameVul2(String username);
+
+ 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..ba9c3f7f
--- /dev/null
+++ b/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java
@@ -0,0 +1,37 @@
+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.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Design csrf access denied page.
+ *
+ */
+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, ServletException {
+
+ 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/HttpFilter.java b/src/main/java/org/joychou/security/HttpFilter.java
new file mode 100644
index 00000000..2d6354e7
--- /dev/null
+++ b/src/main/java/org/joychou/security/HttpFilter.java
@@ -0,0 +1,78 @@
+package org.joychou.security;
+
+
+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.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 HttpFilter implements Filter {
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+
+ }
+
+ private final Logger logger= LoggerFactory.getLogger(HttpFilter.class);
+
+ @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;
+ for (String uri: WebConfig.getReferUris()) {
+ if ( matcher.match (uri, request.getRequestURI()) ) {
+ isMatch = true;
+ break;
+ }
+ }
+
+ if (isMatch) {
+ if (WebConfig.getReferSecEnabled()) {
+ // Check referer for all GET requests with callback parameters.
+ for (String callback: WebConfig.getCallbacks()) {
+ if (request.getMethod().equals("GET") && StringUtils.isNotBlank(request.getParameter(callback)) ){
+ // If the check of referer fails, a 403 forbidden error page will be returned.
+ if (!SecurityUtil.checkURLbyEndsWith(refer, WebConfig.getReferWhitelist())){
+ logger.info("[-] URL: " + request.getRequestURL() + "?" + request.getQueryString() + "\t"
+ + "Referer: " + refer);
+ response.sendRedirect("https://test.joychou.org/error3.html");
+ return;
+ }
+ }
+ }
+ }
+ }
+
+
+
+ filterChain.doFilter(req, res);
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
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..75765b02
--- /dev/null
+++ b/src/main/java/org/joychou/security/LoginSuccessHandler.java
@@ -0,0 +1,46 @@
+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.DefaultSavedRequest;
+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.net.URI;
+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);
+ if (savedRequest != null) {
+ logger.info("Original url is: " + savedRequest.getRedirectUrl());
+ }
+
+ Map content = new HashMap<>();
+ content.put("code", "0");
+ content.put("message", "Login success");
+
+ // google ajax and sendRedirect
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ response.getWriter().write(JSON.toJSONString(content));
+ }
+}
diff --git a/src/main/java/org/joychou/security/SSRFChecker.java b/src/main/java/org/joychou/security/SSRFChecker.java
new file mode 100644
index 00000000..6ecba2f2
--- /dev/null
+++ b/src/main/java/org/joychou/security/SSRFChecker.java
@@ -0,0 +1,136 @@
+package org.joychou.security;
+
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URL;
+import org.apache.commons.net.util.SubnetUtils;
+
+public class SSRFChecker {
+
+ private static int connectTime = 5*1000; // 设置连接超时时间5s
+
+ /**
+ * 解析url的ip,判断ip是否是内网ip,所以TTL设置为0的情况不适用。
+ * url只允许https或者http,并且设置默认连接超时时间。
+ * 该修复方案会主动请求重定向后的链接。最好用Hook方式获取到所有url后,进行判断,代码待续…
+ *
+ * @param url check的url
+ * @return 安全返回true,危险返回false
+ */
+ public static Boolean checkSSRF(String url) {
+
+ HttpURLConnection connection;
+ String finalUrl = url;
+ try {
+ do {
+ // 判断当前请求的URL是否是内网ip
+ Boolean bRet = isInnerIPByUrl(finalUrl);
+ if (bRet) {
+ 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;
+ // System.out.println("redirected url: " + finalUrl);
+ } 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 isInnerIPByUrl(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 isInnerIp(ip);
+ }
+
+
+ /**
+ * 使用SubnetUtils库判断ip是否在内网网段
+ *
+ * @param strIP ip字符串
+ * @return 如果是内网ip,返回true,否则返回false。
+ */
+ private static boolean isInnerIp(String strIP){
+
+ String blackSubnetlist[] = {"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "0.0.0.0/32"};
+
+ for (String subnet: blackSubnetlist) {
+ SubnetUtils utils = new SubnetUtils(subnet);
+ if (utils.getInfo().isInRange(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 (!url.startsWith("http://") && ! url.startsWith("https://")) {
+ return "";
+ }
+
+ return u.getHost();
+
+ } catch (Exception e) {
+ return "";
+ }
+
+ }
+}
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..65681836
--- /dev/null
+++ b/src/main/java/org/joychou/security/SecurityUtil.java
@@ -0,0 +1,121 @@
+package org.joychou.security;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.util.regex.Pattern;
+
+public class SecurityUtil {
+
+ private static final Pattern FILTER_PATTERN = Pattern.compile("^[a-zA-Z0-9_/\\.-]+$") ;
+
+ protected static Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
+ /**
+ * 通过endsWith判断URL是否合法
+ *
+ * @param url 需要check的url
+ * @param urlwhitelist url白名单list
+ * @return 安全url返回true,危险url返回false
+ */
+ public static Boolean checkURLbyEndsWith(String url, String[] urlwhitelist) {
+ if (null == url) {
+ return false;
+ }
+ try {
+ URI uri = new URI(url);
+
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
+ return false;
+ }
+
+ String host = uri.getHost().toLowerCase();
+ for (String whitelist: urlwhitelist){
+ if (host.endsWith("." + whitelist)) {
+ return true;
+ }
+ }
+
+ return false;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * 解析url的ip,判断ip是否是内网ip,所以TTL设置为0的情况不适用。
+ *
+ * @param url check的url
+ * @return 安全返回true,危险返回false
+ */
+ public static Boolean checkSSRF(String url) {
+ if (SSRFChecker.checkSSRF(url)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Suitable for: TTL isn't set to 0 & Redirect is forbidden.
+ *
+ * @param url The url that needs to check.
+ * @return Safe url returns true. Dangerous url returns false.
+ */
+ public static boolean checkSSRFWithoutRedirect(String url) {
+ return !SSRFChecker.isInnerIPByUrl(url);
+ }
+
+ /**
+ * Check SSRF by host white list.
+ * This is the simplest and most effective method to fix ssrf vul.
+ *
+ * @param url The url that needs to check.
+ * @param hostWlist host whitelist
+ * @return Safe url returns true. Dangerous url returns false.
+ */
+ public static boolean checkSSRFByHostWlist(String url, String[] hostWlist) {
+ return checkURLbyEndsWith(url, hostWlist);
+ }
+
+ /**
+ * 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.indexOf("..") != -1 || temp.charAt(0) == '/') {
+ return null;
+ }
+
+ return filepath;
+ }
+
+
+ public static String cmdFilter(String input) {
+ if (!FILTER_PATTERN.matcher(input).matches()) {
+ return null;
+ }
+
+ return input;
+ }
+}
\ 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..127115f3
--- /dev/null
+++ b/src/main/java/org/joychou/security/WebSecurityConfig.java
@@ -0,0 +1,82 @@
+package org.joychou.security;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+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 javax.servlet.http.HttpServletRequest;
+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"};
+
+ 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());
+
+ // 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()
+ .rememberMe(); // tomcat默认JSESSION会话有效时间为30分钟,所以30分钟不操作会话将过期。为了解决这一问题,引入rememberMe功能。
+ }
+
+ @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/util/WebUtils.java b/src/main/java/org/joychou/util/WebUtils.java
new file mode 100644
index 00000000..685340c6
--- /dev/null
+++ b/src/main/java/org/joychou/util/WebUtils.java
@@ -0,0 +1,26 @@
+package org.joychou.util;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.io.InputStream;
+
+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();
+ }
+}
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 d283d34c..00000000
--- a/src/main/java/org/joychou/utils/Security.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.joychou.utils;
-
-import com.google.common.net.InternetDomainName;
-
-import javax.imageio.ImageIO;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.IOException;
-import java.net.URI;
-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);
- URI uri = new URI(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;
- }
- // 使用uri获取host
- String host = uri.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;
- }
- }
-
-
- /**
- * @param file
- * @desc 判断文件内容是否是图片
- */
- public static boolean isImage(File file) throws IOException {
- BufferedImage bi = ImageIO.read(file);
- if (bi == null) {
- return false;
- }
- return true;
- }
-}
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index cdb84e53..d144baa5 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,4 +1,35 @@
+spring.datasource.url=jdbc:mysql://localhost:3306/java_sec_code?AllowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC
+spring.datasource.username=root
+spring.datasource.password=woshishujukumima
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+mybatis.mapper-locations=classpath:mapper/*.xml
+
# Spring Boot Actuator Vulnerable Config
management.security.enabled=false
-logging.config=classpath:logback-online.xml
\ No newline at end of file
+# logging.config=classpath:logback-online.xml
+
+
+
+### check referer configuration begins ###
+joychou.security.referer.enabled = false
+joychou.security.referer.hostwhitelist = 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/**
+# method for CSRF check
+joychou.security.csrf.method = POST
+### csrf configuration ends ###
+
+
+### jsonp configuration begins ### # auto convert json to jsonp
+# callback parameters name
+joychou.security.jsonp.callback = callback, _callback
+### jsonp configuration ends ###
\ 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/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml
new file mode 100644
index 00000000..df4344e6
--- /dev/null
+++ b/src/main/resources/mapper/UserMapper.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ select * from users where username like '%${_parameter}%'
+
+
+
+ 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..e3a3d25e
--- /dev/null
+++ b/src/main/resources/templates/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+ Home Page
+
+
+ Hello .
+ Welcome to login java-sec-code application. Application Infomation
+
+ CmdInject
+ JSONP
+ 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..1b069872
--- /dev/null
+++ b/src/main/resources/templates/login.html
@@ -0,0 +1,64 @@
+
+
+
+
+ Login
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/templates/upload.html b/src/main/resources/templates/upload.html
index 10898e0b..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
index ce056aa7..66a6f64d 100644
--- a/src/main/resources/templates/uploadPic.html
+++ b/src/main/resources/templates/uploadPic.html
@@ -4,7 +4,7 @@
file upload only picture
-
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
+
+
+
+
+