findUsersByUsernameNative(String username);
+}
\ No newline at end of file
diff --git a/src/main/java/top/whgojp/modules/sqli/service/HsqliService.java b/src/main/java/top/whgojp/modules/sqli/service/HsqliService.java
deleted file mode 100644
index 61dae36..0000000
--- a/src/main/java/top/whgojp/modules/sqli/service/HsqliService.java
+++ /dev/null
@@ -1,11 +0,0 @@
-//package top.whgojp.modules.sqli.service;
-//
-///**
-// * @description <功能描述>
-// * @author: whgojp
-// * @email: whgojp@foxmail.com
-// * @Date: 2024/8/5 18:11
-// */
-//public interface HsqliService {
-//
-//}
diff --git a/src/main/java/top/whgojp/modules/ssrf/controller/SsrfController.java b/src/main/java/top/whgojp/modules/ssrf/controller/SsrfController.java
index 24e3798..6a864f0 100644
--- a/src/main/java/top/whgojp/modules/ssrf/controller/SsrfController.java
+++ b/src/main/java/top/whgojp/modules/ssrf/controller/SsrfController.java
@@ -10,10 +10,14 @@
import org.springframework.web.bind.annotation.*;
import top.whgojp.common.utils.CheckUserInput;
+import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
+import java.io.IOException;
import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
/**
* @description SSRF-服务端请求伪造
@@ -32,11 +36,27 @@ public String fileUpload() {
return "vul/ssrf/ssrf";
}
- @ApiOperation(value = "漏洞环境:服务端请求伪造", notes = "原生漏洞环境,未做任何限制,可调用URLConnection发起任意请求,探测内网服务、读取文件")
- @GetMapping("/vul1-URLConnection")
+ @ApiOperation(value = "模拟内网元数据服务", notes = "用于SSRF场景演示,模拟攻击者通过服务端访问内网或云元数据接口")
+ @GetMapping("/internal/metadata")
+ @ResponseBody
+ public String internalMetadata() {
+ return "instance-id: i-javaseclab-ssrf\n"
+ + "role: internal-admin\n"
+ + "token: javaseclab-metadata-token\n"
+ + "source: 127.0.0.1";
+ }
+
+ @ApiOperation(value = "模拟跳转链路", notes = "用于演示SSRF修复时必须禁用自动跳转,或对每一跳重新校验")
+ @GetMapping("/redirect")
+ public void redirect(@RequestParam String target, HttpServletResponse response) throws IOException {
+ response.sendRedirect(target);
+ }
+
+ @ApiOperation(value = "漏洞场景:服务端请求伪造", notes = "原生漏洞场景,未做任何限制,可调用URLConnection发起任意请求,探测内网服务、读取文件")
+ @GetMapping("/vul")
@ResponseBody
@ApiImplicitParam(name = "url", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
- public String vul1URLConnection(@ApiParam(name = "url", value = "请求参数", required = true) @RequestParam String url) {
+ public String vul(@ApiParam(name = "url", value = "请求参数", required = true) @RequestParam String url) {
try {
URL u = new URL(url);
URLConnection conn = u.openConnection(); // 这里以URLConnection作为演示
@@ -59,10 +79,10 @@ public String vul1URLConnection(@ApiParam(name = "url", value = "请求参数",
private CheckUserInput checkUserInput;
@ApiOperation(value = "安全代码:请求白名单过滤", notes = "判断协议,对请求URL做白名单过滤")
- @GetMapping("/safe1-WhiteList")
+ @GetMapping("/safe")
@ResponseBody
@ApiImplicitParam(name = "url", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
- public String safe1WhiteList(@ApiParam(name = "url", value = "请求参数", required = true) @RequestParam String url) {
+ public String safe(@ApiParam(name = "url", value = "请求参数", required = true) @RequestParam String url) {
if (!checkUserInput.isHttp(url)) {
return "检测到不是http(s)协议!";
} else if (!checkUserInput.ssrfWhiteList(url)) {
@@ -70,8 +90,11 @@ public String safe1WhiteList(@ApiParam(name = "url", value = "请求参数", req
} else {
try {
URL u = new URL(url);
- URLConnection conn = u.openConnection(); // 这里以URLConnection作为演示
- BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+ HttpURLConnection conn = (HttpURLConnection) u.openConnection();
+ conn.setInstanceFollowRedirects(false);
+ conn.setConnectTimeout(3000);
+ conn.setReadTimeout(3000);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
String content;
StringBuilder html = new StringBuilder();
html.append("");
diff --git a/src/main/java/top/whgojp/modules/ssti/controller/SSTIController.java b/src/main/java/top/whgojp/modules/ssti/controller/SSTIController.java
index 59c0137..702b8b5 100644
--- a/src/main/java/top/whgojp/modules/ssti/controller/SSTIController.java
+++ b/src/main/java/top/whgojp/modules/ssti/controller/SSTIController.java
@@ -5,18 +5,12 @@
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.expression.EvaluationContext;
-import org.springframework.expression.Expression;
-import org.springframework.expression.ExpressionParser;
-import org.springframework.expression.spel.standard.SpelExpressionParser;
-import org.springframework.expression.spel.support.SimpleEvaluationContext;
-import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
-import top.whgojp.common.utils.R;
import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -38,40 +32,43 @@ public String ssti() {
return "vul/ssti/ssti";
}
- @ApiOperation(value = "漏洞环境:Thymeleaf模板注入", notes = "如果参数未经过滤,攻击者可以注入恶意模板参数,执行任意代码。")
+ @ApiOperation(value = "漏洞场景:Thymeleaf模板注入", notes = "如果参数未经过滤,攻击者可以注入恶意模板参数,执行任意代码。")
@ApiImplicitParam(name = "para", value = "用户输入参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
- @GetMapping("/vul1-thymeleaf")
- public String sstiVul(@ApiParam(name = "para", value = "用户输入参数", required = true) @RequestParam String para, Model model) {
+ @GetMapping("/vul1")
+ public String vul1(@ApiParam(name = "para", value = "用户输入参数", required = true) @RequestParam String para, Model model) {
// model.addAttribute("para", para);
// return "vul/ssti/vul"; // 将参数 para 传递到模板 "vul/ssti/template"
// 用户输入直接拼接到模板路径,可能导致SSTI(服务器端模板注入)漏洞
- return "/vul/ssti/" + para;
+ return "vul/ssti/" + para;
}
- @GetMapping("/vul2-thymeleaf/{path}")
- public void sstiVul2(@PathVariable String path) {
- log.info("SSTI注入:"+path);
+ @GetMapping("/vul2/{path}")
+ public String vul2(@PathVariable String path) {
+ log.info("SSTI注入:" + path);
+ return "vul/ssti/" + path;
}
- @GetMapping("/vul3-thymeleaf")
- public String sstiVul3(@ApiParam(name = "para", value = "用户输入参数", required = true) @RequestParam String para, Model model) {
+ @GetMapping("/vul3")
+ public String vul3(@ApiParam(name = "para", value = "用户输入参数", required = true) @RequestParam String para, Model model) {
model.addAttribute("templateContent", para);
return "vul/ssti/vul"; // 将参数 para 传递到模板 "vul/ssti/vul"
}
- @GetMapping("/safe-thymeleaf")
- public String sstiSafe(@ApiParam(name = "para", value = "用户输入参数", required = true) @RequestParam String para, Model model) {
+ @GetMapping("/safe1")
+ public String safe1(@ApiParam(name = "para", value = "用户输入参数", required = true) @RequestParam String para, Model model) {
List white_list = new ArrayList<>(Arrays.asList("vul", "ssti"));
if (white_list.contains(para)){
- return "vul/ssti" + para;
+ return "vul/ssti/" + para;
} else{
return "common/401";
}
}
@GetMapping("/safe2/{path}")
- public void sstiSafe2(@PathVariable String path, HttpServletResponse response) {
- log.info("SSTI注入:"+path);
+ public void safe2(@PathVariable String path, HttpServletResponse response) throws IOException {
+ log.info("SSTI注入:" + path);
+ response.setContentType("text/plain;charset=UTF-8");
+ response.getWriter().write("已跳过视图解析,输入路径:" + path);
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/top/whgojp/modules/system/mapper/UserMapper.java b/src/main/java/top/whgojp/modules/system/mapper/UserMapper.java
index 6a87a44..10e67bf 100644
--- a/src/main/java/top/whgojp/modules/system/mapper/UserMapper.java
+++ b/src/main/java/top/whgojp/modules/system/mapper/UserMapper.java
@@ -19,6 +19,11 @@ public interface UserMapper extends BaseMapper {
int updatePasswordByUsername(@Param("username") String username,@Param("password") String password);
+ // 水平越权
+ User getAllByUsername(@Param("username") String username);
+
+ // 垂直越权
+
}
diff --git a/src/main/java/top/whgojp/modules/xss/config/XssWebSocketConfig.java b/src/main/java/top/whgojp/modules/xss/config/XssWebSocketConfig.java
new file mode 100644
index 0000000..e1e67f6
--- /dev/null
+++ b/src/main/java/top/whgojp/modules/xss/config/XssWebSocketConfig.java
@@ -0,0 +1,18 @@
+package top.whgojp.modules.xss.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.lang.NonNull;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
+import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
+
+@Configuration
+@EnableWebSocket
+public class XssWebSocketConfig implements WebSocketConfigurer {
+
+ @Override
+ public void registerWebSocketHandlers(@NonNull WebSocketHandlerRegistry registry) {
+ registry.addHandler(new XssWebSocketHandler(), "/xss/websocket")
+ .setAllowedOrigins("*"); // 故意允许所有源,用于演示XSS风险
+ }
+}
diff --git a/src/main/java/top/whgojp/modules/xss/config/XssWebSocketHandler.java b/src/main/java/top/whgojp/modules/xss/config/XssWebSocketHandler.java
new file mode 100644
index 0000000..18488ef
--- /dev/null
+++ b/src/main/java/top/whgojp/modules/xss/config/XssWebSocketHandler.java
@@ -0,0 +1,53 @@
+package top.whgojp.modules.xss.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.lang.NonNull;
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.handler.TextWebSocketHandler;
+
+import java.io.IOException;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+@Slf4j
+public class XssWebSocketHandler extends TextWebSocketHandler {
+
+ private static final CopyOnWriteArraySet sessions = new CopyOnWriteArraySet<>();
+
+ @Override
+ public void afterConnectionEstablished(@NonNull WebSocketSession session) {
+ sessions.add(session);
+ log.info("WebSocket connection established - Current connections: {}", sessions.size());
+ try {
+ session.sendMessage(new TextMessage("Connected successfully"));
+ } catch (IOException e) {
+ log.error("Failed to send welcome message: {}", e.getMessage());
+ }
+ }
+
+ @Override
+ protected void handleTextMessage(@NonNull WebSocketSession session, @NonNull TextMessage message) {
+ log.info("Received message: {}", message.getPayload());
+ // 故意不过滤消息内容,用于演示XSS风险
+ broadcast(message);
+ }
+
+ @Override
+ public void afterConnectionClosed(@NonNull WebSocketSession session, @NonNull CloseStatus status) {
+ sessions.remove(session);
+ log.info("WebSocket connection closed - Current connections: {}", sessions.size());
+ }
+
+ private void broadcast(TextMessage message) {
+ for (WebSocketSession session : sessions) {
+ try {
+ if (session.isOpen()) {
+ session.sendMessage(message);
+ }
+ } catch (IOException e) {
+ log.error("Failed to send message to session {}: {}", session.getId(), e.getMessage());
+ }
+ }
+ }
+}
diff --git a/src/main/java/top/whgojp/modules/xss/controller/ActionEnterRewrite.java b/src/main/java/top/whgojp/modules/xss/controller/ActionEnterRewrite.java
index cae747e..6c197b6 100644
--- a/src/main/java/top/whgojp/modules/xss/controller/ActionEnterRewrite.java
+++ b/src/main/java/top/whgojp/modules/xss/controller/ActionEnterRewrite.java
@@ -14,10 +14,6 @@
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
-/**
- * 描述:
- * 创建人: 慌途L
- */
@Slf4j
public class ActionEnterRewrite {
private HttpServletRequest request;
diff --git a/src/main/java/top/whgojp/modules/xss/controller/DomController.java b/src/main/java/top/whgojp/modules/xss/controller/DomController.java
index d745015..e5034c8 100644
--- a/src/main/java/top/whgojp/modules/xss/controller/DomController.java
+++ b/src/main/java/top/whgojp/modules/xss/controller/DomController.java
@@ -14,7 +14,7 @@
* @Date: 2024/5/23 17:25
*/
@Slf4j
-@Api(value = "ReflectController", tags = "跨站脚本-Dom型XSS")
+@Api(value = "DomController", tags = "跨站脚本-DOM型XSS")
@Controller
@CrossOrigin(origins = "*")
@RequestMapping("/xss/dom")
diff --git a/src/main/java/top/whgojp/modules/xss/controller/FileUtils.java b/src/main/java/top/whgojp/modules/xss/controller/FileUtils.java
index d5ab2ec..9a63996 100644
--- a/src/main/java/top/whgojp/modules/xss/controller/FileUtils.java
+++ b/src/main/java/top/whgojp/modules/xss/controller/FileUtils.java
@@ -11,48 +11,47 @@
import java.io.File;
import java.io.IOException;
-import java.util.Random;
+import java.util.UUID;
+
-/**
- * 功能 : 上传文件工具类
- * 创建人 : 慌途L
- */
@Slf4j
public class FileUtils {
public static String upLoadFile(MultipartFile file, String path) {
-
- if(file.isEmpty()){
+ if (file == null || file.isEmpty()) {
log.info("文件为空!");
return null;
}
+
String fileName = file.getOriginalFilename();
- int size = (int) file.getSize();
- log.info(fileName + "-->" + size);
-
- // 取得文件的后缀名。
- String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toUpperCase();
-
- String newFileName =
- System.currentTimeMillis() / 1000 + new Random().nextInt(100000)+"." + ext;
+ log.info("上传文件: {} - 大小: {}", fileName, file.getSize());
- //String path = "F:/test" ;
- File dest = new File(path + "/" + newFileName);
- if(!dest.getParentFile().exists()){ //判断文件父目录是否存在
- dest.getParentFile().mkdir();
+ String ext = getFileExtension(fileName);
+ String newFileName = generateUniqueFileName(ext);
+
+ return saveFile(file, path, newFileName);
+ }
+
+ private static String getFileExtension(String fileName) {
+ return fileName.substring(fileName.lastIndexOf(".") + 1).toUpperCase();
+ }
+
+ private static String generateUniqueFileName(String ext) {
+ return UUID.randomUUID().toString() + "." + ext;
+ }
+
+ private static String saveFile(MultipartFile file, String path, String newFileName) {
+ File dest = new File(path, newFileName);
+ if (!dest.getParentFile().exists()) {
+ dest.getParentFile().mkdirs();
}
+
try {
- file.transferTo(dest); //保存文件
+ file.transferTo(dest);
return newFileName;
- } catch (IllegalStateException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- return null;
} catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
+ log.error("文件保存失败", e);
return null;
}
}
}
-
diff --git a/src/main/java/top/whgojp/modules/xss/controller/JsonpController.java b/src/main/java/top/whgojp/modules/xss/controller/JsonpController.java
new file mode 100644
index 0000000..3c4a30f
--- /dev/null
+++ b/src/main/java/top/whgojp/modules/xss/controller/JsonpController.java
@@ -0,0 +1,17 @@
+package top.whgojp.modules.xss.controller;
+
+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;
+
+@RestController
+@RequestMapping("/xss")
+public class JsonpController {
+
+ @GetMapping("/jsonp")
+ public String handleJsonp(@RequestParam String callback) {
+ // 故意不验证callback参数,直接拼接返回
+ return callback + "(" + "{\"message\": \"Hello from JSONP\"}" + ");";
+ }
+}
diff --git a/src/main/java/top/whgojp/modules/xss/controller/OtherController.java b/src/main/java/top/whgojp/modules/xss/controller/OtherController.java
index 774050e..e79a44b 100644
--- a/src/main/java/top/whgojp/modules/xss/controller/OtherController.java
+++ b/src/main/java/top/whgojp/modules/xss/controller/OtherController.java
@@ -84,7 +84,7 @@ public R hackCookie(@RequestParam String cookie, HttpServletRequest request) {
private UploadUtil uploadUtil;
// 文件上传接口
- @ApiOperation(value = "漏洞环境:文件上传导致存储XSS", notes = "原生漏洞环境,未加任何过滤,Controller接口返回Json类型结果")
+ @ApiOperation(value = "漏洞场景:文件上传导致存储XSS", notes = "上传可被浏览器或预览服务解析的文件,后续访问文件时可能触发XSS")
@RequestMapping("/vul1Upload")
@ResponseBody
@SneakyThrows
@@ -110,6 +110,7 @@ public R vul1Upload(@RequestParam("file") MultipartFile file,
} catch (Exception e) {
return R.error("上传错误,请检查后重新上传:" + e.getMessage());
}
+ // XML解析成功后继续落盘,便于演示“解析 + 可访问文件”组合场景。
case "html":
case "svg":
case "pdf":
@@ -122,14 +123,14 @@ public R vul1Upload(@RequestParam("file") MultipartFile file,
return R.error(res);
}
}
- @ApiOperation(value = "漏洞环境:模版引擎解析导致存储XSS", notes = "")
+ @ApiOperation(value = "漏洞场景:模板引擎不安全渲染导致XSS", notes = "th:utext会把内容作为HTML渲染,th:text会进行转义")
@GetMapping("/vul2OtherTemplate")
- public String vul2OtherTemplate(@RequestParam("content") String content,
+ public String vul2OtherTemplate(@RequestParam("payload") String payload,
@RequestParam("type") String type, Model model) {
if ("html".equals(type)) {
- model.addAttribute("html", content);
+ model.addAttribute("html", payload);
} else if ("text".equals(type)) {
- model.addAttribute("text", content);
+ model.addAttribute("text", payload);
}
return "vul/xss/other";
}
diff --git a/src/main/java/top/whgojp/modules/xss/controller/PostMessageController.java b/src/main/java/top/whgojp/modules/xss/controller/PostMessageController.java
new file mode 100644
index 0000000..e5d8e82
--- /dev/null
+++ b/src/main/java/top/whgojp/modules/xss/controller/PostMessageController.java
@@ -0,0 +1,20 @@
+package top.whgojp.modules.xss.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+@RequestMapping("/xss/postmessage")
+public class PostMessageController {
+
+ @GetMapping("/sender")
+ public String sender() {
+ return "vul/xss/postmessage/sender";
+ }
+
+ @GetMapping("/receiver")
+ public String receiver() {
+ return "vul/xss/postmessage/receiver";
+ }
+}
diff --git a/src/main/java/top/whgojp/modules/xss/controller/ReflectController.java b/src/main/java/top/whgojp/modules/xss/controller/ReflectController.java
index 38e65e9..6266dd4 100644
--- a/src/main/java/top/whgojp/modules/xss/controller/ReflectController.java
+++ b/src/main/java/top/whgojp/modules/xss/controller/ReflectController.java
@@ -12,11 +12,11 @@
import org.thymeleaf.util.StringUtils;
import top.whgojp.common.utils.CheckUserInput;
import top.whgojp.common.utils.R;
+import top.whgojp.modules.xss.controller.base.XssBaseController;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -27,65 +27,57 @@
* @Date: 2024/5/20 16:55
*/
@Slf4j
-@Api(value = "ReflectController", tags = "跨站脚本-反射型XSS")
+@Api(value = "ReflectController", tags = "跨站脚本 - 反射型XSS")
@Controller
@CrossOrigin(origins = "*")
@RequestMapping("/xss/reflect")
-public class ReflectController {
+public class ReflectController extends XssBaseController {
+
@Autowired
private CheckUserInput checkUserInput;
- @RequestMapping("")
- public String xssReflect() {
- return "vul/xss/reflect";
- }
- @RequestMapping("/vul")
- public String xssReflectVul() {
- return "vul/xss/reflect-vul";
- }
- @RequestMapping("/safe")
- public String xssReflectSafe() {
- return "vul/xss/reflect-safe";
- }
+ @RequestMapping("/{view}")
+ public String reflect(@PathVariable String view) {
+ return isValidView(view) ? "vul/xss/reflect/" + view : "error/404";
+ }
- @ApiOperation(value = "漏洞环境:GET型与POST型", notes = "原生漏洞环境,未加任何过滤,Controller接口返回Json类型结果")
- @RequestMapping("/vul1ReflectRaw")
+ @ApiOperation(value = "漏洞场景:GET型与POST型", notes = "原生漏洞场景,未加任何过滤,Controller接口返回JSON类型结果。JSON本身通常不会直接触发XSS,但前端不安全渲染JSON字段时可能触发")
+ @RequestMapping("/vul1")
@ResponseBody
- @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
- public R vul1ReflectRaw(@ApiParam(name = "content", value = "请求参数", required = true) @RequestParam String content) {
- log.info("反射型XSS:" + content);
- return R.ok(content);
+ @ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
+ public R vul1(@ApiParam(name = "payload", value = "请求参数", required = true) @RequestParam String payload) {
+ return handleXssPayload(payload, "反射型-GET/POST型", false);
}
- @ApiOperation(value = "漏洞环境:String", notes = "原生漏洞环境,未加任何过滤,Controller接口返回String")
- @GetMapping("/vul1ReflectRawString")
+ @ApiOperation(value = "漏洞场景:String", notes = "原生漏洞场景,未加任何过滤,Controller接口返回String")
+ @GetMapping("/vul2")
@ResponseBody
- @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
- public String vul1ReflectRawString(@ApiParam(name = "content", value = "请求参数", required = true) @RequestParam String content) {
-
- return content;
+ @ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
+ public String vul2(@ApiParam(name = "payload", value = "请求参数", required = true) @RequestParam String payload) {
+ log.info("[+]XSS-反射型-String型:" + payload);
+ return payload;
}
@SneakyThrows
- @ApiOperation(value = "漏洞环境:Content-Type问题", notes = "Tomcat内置HttpServletResponse,Content-Type导致反射XSS")
- @GetMapping("/vul2ReflectContentType")
+ @ApiOperation(value = "漏洞场景:Content-Type问题", notes = "响应Content-Type决定浏览器解析方式,不可信内容以text/html返回时可能导致反射XSS")
+ @GetMapping("/vul3")
@ResponseBody
@ApiImplicitParams({
@ApiImplicitParam(name = "type", value = "类型", dataType = "String", paramType = "query", dataTypeClass = String.class),
- @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
+ @ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
})
- public void vul2ReflectContentType(@ApiParam(name = "type", value = "类型", required = true) @RequestParam String type, @ApiParam(name = "content", value = "请求参数", required = true) @RequestParam String content, HttpServletResponse response) {
+ public void vul3(@ApiParam(name = "type", value = "类型", required = true) @RequestParam String type, @ApiParam(name = "payload", value = "请求参数", required = true) @RequestParam String payload, HttpServletResponse response) {
switch (type) {
case "html":
- response.getWriter().print(content);
- log.info("反射型XSS,Content-Type:text/html;charset=utf-8:" + content);
+ response.getWriter().print(payload);
+ log.info("[+]XSS-反射性-Content-Type:text/html;charset=utf-8:" + payload);
response.setContentType("text/html;charset=utf-8");
response.getWriter().flush();
break;
case "plain":
- log.info("反射型XSS,Content-Type:text/plain;charset=utf-8:" + content);
- response.getWriter().print(content);
- response.setContentType("text/plain;charset=utf-8"); // response默认返回Content-Type类型是text/plain
+ log.info("[+]XSS-反射性-Content-Type:text/plain;charset=utf-8:" + payload);
+ response.getWriter().print(payload);
+ response.setContentType("text/plain;charset=utf-8");
response.getWriter().flush();
break;
default:
@@ -95,44 +87,51 @@ public void vul2ReflectContentType(@ApiParam(name = "type", value = "类型", re
break;
}
}
+
private static final String WHITELIST_REGEX = "^[a-zA-Z0-9_\\s]+$";
private static final Pattern pattern = Pattern.compile(WHITELIST_REGEX);
- @ApiOperation(value = "安全代码:用户输入验证和过滤", notes = "对用户输入的数据进行验证和过滤,确保不包含恶意代码。使用白名单过滤,只允许特定类型的输入,如纯文本或指定格式的数据")
- @RequestMapping("/safe1CheckUserInput")
+ @ApiOperation(value = "安全代码:用户输入验证和过滤", notes = "使用白名单限制输入格式,适合约束字段类型;最终仍需根据输出位置进行上下文编码")
+ @GetMapping("/safe1")
@ResponseBody
@ApiImplicitParams({
@ApiImplicitParam(name = "type", value = "类型", dataType = "String", paramType = "query", dataTypeClass = String.class),
@ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
})
- public R safe1CheckUserInput(@ApiParam(name = "type", value = "类型", required = true) @RequestParam String type, @ApiParam(name = "content", value = "请求参数", required = true) @RequestParam String content) {
+ public R safe1(@ApiParam(name = "type", value = "类型", required = true) @RequestParam String type, @ApiParam(name = "payload", value = "请求参数", required = true) @RequestParam String payload) {
String filterContented = "";
switch (type) {
case "frontEnd":
- filterContented = content; // 前端过滤后传递过来 后端未进行处理(同样存在安全问题)
+ log.info("[-]XSS-反射性-前端白名单过滤:" + payload);
+ filterContented = payload; // 前端过滤后传递过来 后端未进行处理(同样存在安全问题)
break;
case "backEnd":
- Matcher matcher = pattern.matcher(content);
- if (matcher.matches()){
- return R.ok(content);
- }else return R.error("输入内容包含非法字符,请检查输入");
+ log.info("[-]XSS-反射性-后端白名单过滤:" + payload);
+ Matcher matcher = pattern.matcher(payload);
+ if (matcher.matches()) {
+ return R.ok(payload);
+ } else return R.error("输入内容包含非法字符,请检查输入");
}
return R.ok(filterContented);
}
- @ApiOperation(value = "安全代码:内容安全策略-CSP防护", notes = "内容安全策略(Content Security Policy)是一种由浏览器实施的安全机制,旨在减少和防范跨站脚本攻击(XSS)等安全威胁。它通过允许网站管理员定义哪些内容来源是可信任的,从而防止恶意内容的加载和执行")
- @RequestMapping("/safe2CSP")
+
+ @ApiOperation(value = "安全代码:内容安全策略-CSP防护", notes = "内容安全策略(Content Security Policy)是由浏览器实施的额外防护层,可降低恶意脚本加载和执行风险,但不能替代输出编码与安全模板/DOM用法")
+ @GetMapping("/safe2")
@ResponseBody
- @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
- public String safe2CSP(@ApiParam(name = "content", value = "请求参数", required = true) @RequestParam String content,HttpServletResponse response) {
- response.setHeader("Content-Security-Policy","default-src self");
- response.setHeader("Content-Security-Policy-Report-Only", "default-src 'self'; other-uri /xss/reflect/csp-other-endpoint");
- return content;
+ @ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
+ public String safe2(@ApiParam(name = "payload", value = "请求参数", required = true) @RequestParam String payload, HttpServletResponse response) {
+ response.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self'");
+ response.setHeader("Content-Security-Policy-Report-Only", "default-src 'self'; report-uri /xss/reflect/csp-report-endpoint");
+ log.info("[-]XSS-反射性-内容安全策略-CSP防护:" + payload);
+ return payload;
}
+
@GetMapping("/a-safe2-CSP-front")
- public String safeCSPFront(){
+ public String safeCSPFront() {
return "vul/xss/csp-protect";
}
+
@PostMapping("/csp-report-endpoint")
public void receiveCSPReport(@RequestBody String reportData) {
// 获取当前时间
@@ -152,27 +151,30 @@ public void receiveCSPReport(@RequestBody String reportData) {
// System.err.println("Error writing CSP violation other to file: " + e.getMessage());
// }
}
- @ApiOperation(value = "安全代码:特殊字符实体转义", notes = "特殊字符实体转义是一种将 HTML 中的特殊字符转换为预定义实体表示的过程。这种转义是为了确保在 HTML 页面中正确显示特定字符,同时避免它们被浏览器误解为 HTML 标签或JavaScript代码的一部分,从而导致页面结构混乱或安全漏洞。")
- @RequestMapping("/safe3EntityEscape")
+
+ @ApiOperation(value = "安全代码:HTML正文输出编码", notes = "将HTML正文文本中的特殊字符编码为实体,避免浏览器把不可信数据解析为HTML标签或JavaScript。不同输出上下文需要使用不同编码策略")
+ @GetMapping("/safe3")
@ResponseBody
@ApiImplicitParams({
@ApiImplicitParam(name = "type", value = "类型", dataType = "String", paramType = "query", dataTypeClass = String.class),
- @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
+ @ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
})
- public R safe3EntityEscape(@ApiParam(name = "type", value = "类型", required = true) @RequestParam String type, @ApiParam(name = "content", value = "请求参数", required = true) @RequestParam String content) {
+ public R safe3(@ApiParam(name = "type", value = "类型", required = true) @RequestParam String type, @ApiParam(name = "payload", value = "请求参数", required = true) @RequestParam String payload) {
String filterContented = "";
- switch (type){
+ switch (type) {
case "manual":
- content = StringUtils.replace(content, "&", "&");
- content = StringUtils.replace(content, "<", "<");
- content = StringUtils.replace(content, ">", ">");
- content = StringUtils.replace(content, "\"", """);
- content = StringUtils.replace(content, "'", "'");
- content = StringUtils.replace(content, "/", "/");
- filterContented = content;
+ payload = StringUtils.replace(payload, "&", "&");
+ payload = StringUtils.replace(payload, "<", "<");
+ payload = StringUtils.replace(payload, ">", ">");
+ payload = StringUtils.replace(payload, "\"", """);
+ payload = StringUtils.replace(payload, "'", "'");
+ payload = StringUtils.replace(payload, "/", "/");
+ filterContented = payload;
+ log.info("[-]XSS-反射型-HTML正文输出编码-手动编码:" + payload);
break;
case "spring":
- filterContented = HtmlUtils.htmlEscape(content);
+ filterContented = HtmlUtils.htmlEscape(payload);
+ log.info("[-]XSS-反射型-HTML正文输出编码-Spring框架:" + payload);
break;
default:
return R.error("参数输入有误!");
@@ -180,11 +182,11 @@ public R safe3EntityEscape(@ApiParam(name = "type", value = "类型", required =
return R.ok(filterContented);
}
- @ApiOperation(value = "安全代码:HttpOnly配置", notes = "HttpOnly是HTTP响应头属性,用于增强Web应用程序安全性。它防止客户端脚本访问(只能通过http/https协议访问)带有HttpOnly标记的 cookie,从而减少跨站点脚本攻击(XSS)的风险。")
- @RequestMapping(value = "/safe4HttpOnly", method = RequestMethod.GET)
+ @ApiOperation(value = "安全代码:HttpOnly配置", notes = "HttpOnly可以阻止客户端脚本直接读取带有该属性的Cookie,降低XSS窃取Cookie的影响,但不能修复XSS本身")
+ @RequestMapping(value = "/safe4", method = RequestMethod.GET)
@ResponseBody
- @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
- public R safe4HttpOnly(@ApiParam(name = "content", value = "请求参数", required = true) String content, HttpServletRequest request,HttpServletResponse response) {
+ @ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
+ public R safe4(@ApiParam(name = "payload", value = "请求参数", required = true) String payload, HttpServletRequest request, HttpServletResponse response) {
Cookie cookie = request.getCookies()[0];
cookie.setHttpOnly(true); // 设置为 HttpOnly
@@ -192,6 +194,6 @@ public R safe4HttpOnly(@ApiParam(name = "content", value = "请求参数", requi
cookie.setPath("/");
response.addCookie(cookie);
- return R.ok("已设置httponly(有效期10分钟),请打开控制台查看cookie属性:"+content);
+ return R.ok("已设置httponly(有效期10分钟),请打开控制台查看cookie属性:" + payload);
}
}
diff --git a/src/main/java/top/whgojp/modules/xss/controller/StoreController.java b/src/main/java/top/whgojp/modules/xss/controller/StoreController.java
index 036c54c..1ddb25d 100644
--- a/src/main/java/top/whgojp/modules/xss/controller/StoreController.java
+++ b/src/main/java/top/whgojp/modules/xss/controller/StoreController.java
@@ -45,14 +45,14 @@ public String xssStore() {
return "vul/xss/store";
}
- @ApiOperation(value = "漏洞环境:原生无过滤", notes = "原生漏洞环境,未加任何过滤,将用户输入存储到数据库中")
- @RequestMapping("/vul1StoreRaw")
+ @ApiOperation(value = "漏洞场景:原生无过滤", notes = "原生漏洞场景,未加任何过滤,将用户输入和User-Agent持久化;后续页面不安全渲染时触发存储型XSS")
+ @PostMapping("/vul")
@ResponseBody
- @ApiImplicitParam(name = "content", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
- public R vul1StoreRaw(@ApiParam(name = "content", value = "请求参数", required = true) @RequestParam String content,HttpServletRequest request) {
- log.info("存储型XSS:" + content);
+ @ApiImplicitParam(name = "payload", value = "请求参数", dataType = "String", paramType = "query", dataTypeClass = String.class)
+ public R vul(@ApiParam(name = "payload", value = "请求参数", required = true) @RequestParam String payload,HttpServletRequest request) {
+ log.info("[+]XSS-存储性-原生无过滤:" + payload);
String ua = request.getHeader("User-Agent");
- final int code = xssService.insertOne(content,ua);
+ final int code = xssService.insertOne(payload,ua);
if (code == 1) {
log.info("插入数据成功!");
return R.ok("插入数据成功!");
diff --git a/src/main/java/top/whgojp/modules/xss/controller/UEditorController.java b/src/main/java/top/whgojp/modules/xss/controller/UEditorController.java
index 0c4546a..8d9b938 100644
--- a/src/main/java/top/whgojp/modules/xss/controller/UEditorController.java
+++ b/src/main/java/top/whgojp/modules/xss/controller/UEditorController.java
@@ -46,14 +46,8 @@ public String ueditor() {
public void getConfigInfo(HttpServletRequest request, HttpServletResponse response) {
response.setContentType("application/json");
- String rootPath = "";
- // 判断当前系统是否是Windows系统
- if (isWindowsSystem()) {
- rootPath = ClassUtils.getDefaultClassLoader().getResource("").getPath() + "static/ueditor/jsp";
- } else {
- // 将config.json文件放在jar包同级目录下
- rootPath = "/Users/whgojp/Desktop/Security/JAVA/JavaSecLab/src/main/resources/static/lib/ueditor/jsp";
- }
+ String rootPath = Objects.requireNonNull(ClassUtils.getDefaultClassLoader().getResource("")).getPath()
+ + "static/lib/ueditor/jsp";
log.info("rootPath:{}", rootPath);
try {
response.setCharacterEncoding("UTF-8");
diff --git a/src/main/java/top/whgojp/modules/xss/controller/base/XssBaseController.java b/src/main/java/top/whgojp/modules/xss/controller/base/XssBaseController.java
new file mode 100644
index 0000000..4ad2018
--- /dev/null
+++ b/src/main/java/top/whgojp/modules/xss/controller/base/XssBaseController.java
@@ -0,0 +1,41 @@
+package top.whgojp.modules.xss.controller.base;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.thymeleaf.util.StringUtils;
+import top.whgojp.common.utils.CheckUserInput;
+import top.whgojp.common.utils.R;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Slf4j
+public abstract class XssBaseController {
+ @Autowired
+ protected CheckUserInput checkUserInput;
+
+ protected R handleXssPayload(String payload, String type, boolean enableFilter) {
+ if (StringUtils.isEmpty(payload)) {
+ return R.error("参数不能为空");
+ }
+
+ log.info("[+]XSS-{}-收到payload:{}", type, payload);
+
+ if (enableFilter) {
+ String filteredPayload = checkUserInput.filter(payload);
+ log.info("[+]XSS-{}-过滤后:{}", type, filteredPayload);
+ return R.ok(filteredPayload);
+ }
+
+ return R.ok(payload);
+ }
+
+ protected String getUserAgent(HttpServletRequest request) {
+ String ua = request.getHeader("User-Agent");
+ return StringUtils.isEmpty(ua) ? "unknown" : ua;
+ }
+
+ protected boolean isValidView(String view) {
+ return !StringUtils.isEmpty(view) &&
+ (view.equals("vul") || view.equals("safe"));
+ }
+}
diff --git a/src/main/java/top/whgojp/modules/xss/service/impl/XssServiceImpl.java b/src/main/java/top/whgojp/modules/xss/service/impl/XssServiceImpl.java
index 034a024..164e309 100644
--- a/src/main/java/top/whgojp/modules/xss/service/impl/XssServiceImpl.java
+++ b/src/main/java/top/whgojp/modules/xss/service/impl/XssServiceImpl.java
@@ -8,7 +8,6 @@
import top.whgojp.modules.xss.service.XssService;
import top.whgojp.modules.xss.mapper.XssMapper;
import org.springframework.stereotype.Service;
-
import java.util.List;
/**
@@ -18,30 +17,42 @@
*/
@Slf4j
@Service
-public class XssServiceImpl extends ServiceImpl
- implements XssService{
+public class XssServiceImpl extends ServiceImpl implements XssService {
@Autowired
private XssMapper xssMapper;
@Override
public int insertOne(String content, String ua) {
- final int code = xssMapper.insertAll(content,ua,DateUtil.now());
- return code;
+ try {
+ log.info("插入XSS记录 - content: {}, ua: {}", content, ua);
+ final int code = xssMapper.insertAll(content,ua,DateUtil.now());
+ return code;
+ } catch (Exception e) {
+ log.error("插入XSS记录失败", e);
+ return 0;
+ }
}
@Override
public List selectAll() {
- List xssList = xssMapper.selectAll();
- return xssList;
+ try {
+ List xssList = xssMapper.selectAll();
+ return xssList;
+ } catch (Exception e) {
+ log.error("查询XSS记录失败", e);
+ return null;
+ }
}
@Override
public int deleteById(int id) {
- int i = xssMapper.deleteById(id);
- return i;
+ try {
+ log.info("删除XSS记录 - id: {}", id);
+ int i = xssMapper.deleteById(id);
+ return i;
+ } catch (Exception e) {
+ log.error("删除XSS记录失败 - id: {}", id, e);
+ return 0;
+ }
}
}
-
-
-
-
diff --git a/src/main/java/top/whgojp/modules/xxe/controller/XXEController.java b/src/main/java/top/whgojp/modules/xxe/controller/XXEController.java
index e3e9dfc..7f546b9 100644
--- a/src/main/java/top/whgojp/modules/xxe/controller/XXEController.java
+++ b/src/main/java/top/whgojp/modules/xxe/controller/XXEController.java
@@ -17,6 +17,7 @@
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
+import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParser;
@@ -47,9 +48,9 @@ public String xxeVul() {
public String xxeSafe() {
return "vul/xxe/xxe-safe";
}
- @RequestMapping(value = "/vulXMLReader")
+ @RequestMapping(value = "/vul1")
@ResponseBody
- public String vulXMLReader(@RequestParam String payload) {
+ public String vul1(@RequestParam String payload) {
try {
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
StringWriter stringWriter = new StringWriter();
@@ -73,11 +74,11 @@ public void characters(char[] ch, int start, int length) {
/**
- * javax.xml.parsers.SAXParser 是 XMLReader 的替代品,它提供了更多的安全措施,例如默认禁用 DTD 和外部实体的声明,如果需要使用 DTD 或外部实体,可以手动启用它们,并使用相应的安全措施
+ * SAXParser 解析不可信 XML 时同样需要显式关闭 DTD、外部实体和外部 DTD 加载。
*/
- @RequestMapping(value = "/vulSAXParser")
+ @RequestMapping(value = "/vul2")
@ResponseBody
- public String vulSAXParser(@RequestParam String payload) {
+ public String vul2(@RequestParam String payload) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
@@ -100,6 +101,19 @@ public void characters(char[] ch, int start, int length) {
}
}
+ @RequestMapping(value = "/vul3")
+ @ResponseBody
+ public String vul3(@RequestParam String payload) {
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document document = builder.parse(new InputSource(new StringReader(payload)));
+ return formatXmlText(document.getDocumentElement().getTextContent());
+ } catch (Exception e) {
+ return e.toString();
+ }
+ }
+
// @ApiOperation(value = "vul:xmlbeam")
// @RequestMapping(value = "/xmlbeam")
@@ -206,15 +220,17 @@ public void characters(char[] ch, int start, int length) {
// }
- @RequestMapping(value = "/safeXMLReader")
+ @RequestMapping(value = "/safe1")
@ResponseBody
- public String safeXMLReader(@RequestParam String payload) {
+ public String safe1(@RequestParam String payload) {
try {
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
// 禁用外部实体引用,防止XXE攻击
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ xmlReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+ xmlReader.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader("")));
StringWriter stringWriter = new StringWriter();
xmlReader.setContentHandler(new DefaultHandler() {
public void characters(char[] ch, int start, int length) {
@@ -234,9 +250,32 @@ public void characters(char[] ch, int start, int length) {
}
}
- @RequestMapping(value = "/safeBlackList")
+ @RequestMapping(value = "/safe3")
@ResponseBody
- public String safeBlackList(@RequestParam String payload) {
+ public String safe3(@RequestParam String payload) {
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+ factory.setXIncludeAware(false);
+ factory.setExpandEntityReferences(false);
+ setAttributeIfSupported(factory, XMLConstants.ACCESS_EXTERNAL_DTD, "");
+ setAttributeIfSupported(factory, XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
+
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ builder.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader("")));
+ Document document = builder.parse(new InputSource(new StringReader(payload)));
+ return formatXmlText(document.getDocumentElement().getTextContent());
+ } catch (Exception e) {
+ return e.toString();
+ }
+ }
+
+ @RequestMapping(value = "/safe2")
+ @ResponseBody
+ public String safe2(@RequestParam String payload) {
String[] black_list = {"ENTITY", "DOCTYPE"};
for (String keyword : black_list) {
if (payload.toUpperCase().contains(keyword)) {
@@ -246,6 +285,20 @@ public String safeBlackList(@RequestParam String payload) {
return "[-]XML内容安全";
}
+ private String formatXmlText(String text) {
+ if (text == null) {
+ return "";
+ }
+ return text.replace("\n", " ");
+ }
+
+ private void setAttributeIfSupported(DocumentBuilderFactory factory, String name, String value) {
+ try {
+ factory.setAttribute(name, value);
+ } catch (IllegalArgumentException e) {
+ log.warn("XML parser does not support attribute: {}", name);
+ }
+ }
}
diff --git a/src/main/java/top/whgojp/security/SecurityConfigurer.java b/src/main/java/top/whgojp/security/SecurityConfigurer.java
index 230f9bf..51ee6aa 100755
--- a/src/main/java/top/whgojp/security/SecurityConfigurer.java
+++ b/src/main/java/top/whgojp/security/SecurityConfigurer.java
@@ -3,6 +3,8 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -12,15 +14,15 @@
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.security.web.authentication.HttpStatusEntryPoint;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
-import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import top.whgojp.common.config.AuthIgnoreConfig;
import top.whgojp.common.constant.SysConstant;
import top.whgojp.common.filter.ValidateCodeFilter;
-import top.whgojp.common.push.service.EmailPush;
import top.whgojp.security.detail.CustomUserDetailsService;
import top.whgojp.security.handler.CustomLogoutSuccessHandler;
import top.whgojp.security.handler.CustomSavedRequestAwareAuthenticationSuccessHandler;
@@ -43,8 +45,6 @@ public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
private CustomSessionInformationExpiredStrategy sessionInformationExpiredStrategy;
- @Autowired
- private EmailPush emailPush;
@Bean
@Override
@@ -74,13 +74,23 @@ protected void configure(HttpSecurity http) throws Exception {
permitAll.add(SysConstant.LOGIN_PROCESS);
permitAll.add(SysConstant.LOGOUT_URL);
permitAll.add(SysConstant.JWT_AUTH);
+ permitAll.add("/eureka/**");
permitAll.add("/file/**");
permitAll.add("/static/images/**");
permitAll.add("/static/lib/**");
permitAll.add("/static/js/**");
permitAll.add("/static/css/**");
permitAll.add("/static/other/**");
-// permitAll.add("/druid/**");
+ permitAll.add("/images/**");
+ permitAll.add("/lib/**");
+ permitAll.add("/js/**");
+ permitAll.add("/css/**");
+ permitAll.add("/api/**");
+ permitAll.add("/upload/**");
+ permitAll.add("/other/**");
+ permitAll.add("/ssrf/internal/**");
+ permitAll.add("/ssrf/redirect");
+ permitAll.add("/druid/**");
// permitAll.add("/ueditor/**");
String[] urls = permitAll.stream().distinct().toArray(String[]::new);
@@ -91,7 +101,8 @@ protected void configure(HttpSecurity http) throws Exception {
// 权限
http.authorizeRequests(authorize ->
// 开放权限
- authorize.antMatchers(urls).permitAll()
+ authorize.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
+ .antMatchers(urls).permitAll()
.anyRequest().authenticated());
// 使用jwt 关闭session校验
@@ -99,17 +110,14 @@ protected void configure(HttpSecurity http) throws Exception {
// http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
- // 如果不需要验证码校验登录 可以注释掉该行
-// http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);
-
- // 如果不用验证码,注释这个过滤器即可
-// http.addFilterAt(usernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
+ // 登录验证码校验,验证码一次性使用,避免同一验证码被重复提交。
+ http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);
// 添加session管理器 session失效后跳到登录页
http.sessionManagement()
.invalidSessionUrl(SysConstant.LOGIN_URL)
- .maximumSessions(1)
+ .maximumSessions(10)
.expiredSessionStrategy(sessionInformationExpiredStrategy);
http.formLogin()
@@ -118,6 +126,9 @@ protected void configure(HttpSecurity http) throws Exception {
.successHandler(authenticationSuccessHandler())
.failureHandler(customSimpleUrlAuthenticationFailureHandler());
+ // TODO: 2025/1/12 解决登录就报错400状态码问题 GPT害死人啊 注释后就没问题了
+ // 设置自定义的未认证用户访问受保护资源时的响应行为,并在用户未通过认证时返回 HTTP 状态码 400 BAD_REQUEST
+// http.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.BAD_REQUEST));
http.logout()
.logoutSuccessHandler(customLogoutSuccessHandler())
@@ -132,15 +143,37 @@ protected void configure(HttpSecurity http) throws Exception {
}
- // 解决跨域
+ // 全局跨域演示配置。跨源安全模块需要由 Controller 自己控制响应头,避免被全局通配配置污染。
public CorsConfigurationSource corsConfigurationSource() {
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- CorsConfiguration corsConfiguration = new CorsConfiguration();
- corsConfiguration.addAllowedOrigin("*");
- corsConfiguration.addAllowedHeader("*");
- corsConfiguration.addAllowedMethod("*");
- source.registerCorsConfiguration("/**", corsConfiguration);
- return source;
+ return request -> {
+ String uri = request.getRequestURI();
+ if (uri.startsWith("/crossorigin/corsVul")) {
+ CorsConfiguration corsConfiguration = new CorsConfiguration();
+ corsConfiguration.addAllowedOriginPattern("*");
+ corsConfiguration.setAllowCredentials(true);
+ corsConfiguration.addAllowedHeader("*");
+ corsConfiguration.addAllowedMethod("*");
+ return corsConfiguration;
+ }
+ if (uri.startsWith("/crossorigin/corsSafe")) {
+ CorsConfiguration corsConfiguration = new CorsConfiguration();
+ corsConfiguration.addAllowedOrigin("http://127.0.0.1:8080");
+ corsConfiguration.addAllowedOrigin("https://127.0.0.1:8080");
+ corsConfiguration.setAllowCredentials(true);
+ corsConfiguration.addAllowedHeader("Content-Type");
+ corsConfiguration.addAllowedMethod("GET");
+ corsConfiguration.addAllowedMethod("OPTIONS");
+ return corsConfiguration;
+ }
+ if (uri.startsWith("/crossorigin/")) {
+ return null;
+ }
+ CorsConfiguration corsConfiguration = new CorsConfiguration();
+ corsConfiguration.addAllowedOrigin("*");
+ corsConfiguration.addAllowedHeader("*");
+ corsConfiguration.addAllowedMethod("*");
+ return corsConfiguration;
+ };
}
@Bean
@@ -153,7 +186,8 @@ public PasswordEncoder passwordEncoder() {
public AuthenticationSuccessHandler authenticationSuccessHandler() {
CustomSavedRequestAwareAuthenticationSuccessHandler customSavedRequestAwareAuthenticationSuccessHandler = new CustomSavedRequestAwareAuthenticationSuccessHandler();
customSavedRequestAwareAuthenticationSuccessHandler.setDefaultTargetUrl("/index");
- customSavedRequestAwareAuthenticationSuccessHandler.setEmailPush(emailPush);
+ customSavedRequestAwareAuthenticationSuccessHandler.setAlwaysUseDefaultTargetUrl(true);
+// customSavedRequestAwareAuthenticationSuccessHandler.setEmailPush(emailPush);
// customSavedRequestAwareAuthenticationSuccessHandler.setSmsService(smsService);
// customSavedRequestAwareAuthenticationSuccessHandler.setWeChatService(wechatService);
return customSavedRequestAwareAuthenticationSuccessHandler;
@@ -168,7 +202,7 @@ public AuthenticationSuccessHandler authenticationSuccessHandler() {
public LogoutSuccessHandler customLogoutSuccessHandler() {
CustomLogoutSuccessHandler customLogoutSuccessHandler = new CustomLogoutSuccessHandler();
customLogoutSuccessHandler.setDefaultTargetUrl(SysConstant.LOGIN_URL);
- customLogoutSuccessHandler.setEmailPush(emailPush);
+// customLogoutSuccessHandler.setEmailPush(emailPush);
return customLogoutSuccessHandler;
}
@@ -176,10 +210,10 @@ public LogoutSuccessHandler customLogoutSuccessHandler() {
public AuthenticationFailureHandler customSimpleUrlAuthenticationFailureHandler() {
CustomSimpleUrlAuthenticationFailureHandler customSimpleUrlAuthenticationFailureHandler = new CustomSimpleUrlAuthenticationFailureHandler();
customSimpleUrlAuthenticationFailureHandler.setDefaultFailureUrl(SysConstant.LOGIN_URL);
- customSimpleUrlAuthenticationFailureHandler.setEmailPush(emailPush);
+// customSimpleUrlAuthenticationFailureHandler.setEmailPush(emailPush);
return customSimpleUrlAuthenticationFailureHandler;
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/top/whgojp/security/handler/CustomLogoutSuccessHandler.java b/src/main/java/top/whgojp/security/handler/CustomLogoutSuccessHandler.java
index f912df1..7619a19 100755
--- a/src/main/java/top/whgojp/security/handler/CustomLogoutSuccessHandler.java
+++ b/src/main/java/top/whgojp/security/handler/CustomLogoutSuccessHandler.java
@@ -5,7 +5,6 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
-import top.whgojp.common.push.service.EmailPush;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -16,8 +15,6 @@
@Data
@Slf4j
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
- private EmailPush emailPush;
-
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
super.onLogoutSuccess(request, response, authentication);
diff --git a/src/main/java/top/whgojp/security/handler/CustomSavedRequestAwareAuthenticationSuccessHandler.java b/src/main/java/top/whgojp/security/handler/CustomSavedRequestAwareAuthenticationSuccessHandler.java
index 8e7bcec..8d25257 100755
--- a/src/main/java/top/whgojp/security/handler/CustomSavedRequestAwareAuthenticationSuccessHandler.java
+++ b/src/main/java/top/whgojp/security/handler/CustomSavedRequestAwareAuthenticationSuccessHandler.java
@@ -6,9 +6,6 @@
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import top.whgojp.common.event.LoginLogEvent;
-import top.whgojp.common.push.service.EmailPush;
-import top.whgojp.common.push.service.SmsPush;
-import top.whgojp.common.push.service.WechatPush;
import top.whgojp.common.utils.IPUtil;
import top.whgojp.common.utils.SpringContextUtil;
import top.whgojp.modules.system.entity.Log;
@@ -23,11 +20,11 @@
@Slf4j
public class CustomSavedRequestAwareAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
- private EmailPush emailPush;
-
- private SmsPush smsPush;
-
- private WechatPush wechatPush;
+// private EmailPush emailPush;
+//
+// private SmsPush smsPush;
+//
+// private WechatPush wechatPush;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
diff --git a/src/main/java/top/whgojp/security/handler/CustomSimpleUrlAuthenticationFailureHandler.java b/src/main/java/top/whgojp/security/handler/CustomSimpleUrlAuthenticationFailureHandler.java
index 0b80fb8..ebca529 100755
--- a/src/main/java/top/whgojp/security/handler/CustomSimpleUrlAuthenticationFailureHandler.java
+++ b/src/main/java/top/whgojp/security/handler/CustomSimpleUrlAuthenticationFailureHandler.java
@@ -10,10 +10,8 @@
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
-import org.springframework.util.StringUtils;
import top.whgojp.common.constant.SysConstant;
import top.whgojp.common.enums.LoginError;
-import top.whgojp.common.push.service.EmailPush;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -27,15 +25,10 @@ public class CustomSimpleUrlAuthenticationFailureHandler extends SimpleUrlAuthen
private static final String DEFAULT_FAILURE_URL = SysConstant.LOGIN_URL;
- private String defaultFailureUrl;
-
- private EmailPush emailPush;
-
-
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
- super.onAuthenticationFailure(request, response, exception);
setDefaultFailureUrl(determineFailureUrl(exception));
+ super.onAuthenticationFailure(request, response, exception);
log.info("当前异常:"+exception.getMessage());
String loginIp = request.getRemoteHost();
@@ -44,9 +37,6 @@ public void onAuthenticationFailure(HttpServletRequest request, HttpServletRespo
log.info("IP:{} 于 {} 尝试登录系统失败 失败原因:{}", loginIp, loginDate, exception.getMessage());
try {
- // 发邮件
- this.emailPush.send();
-
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
}
@@ -55,24 +45,22 @@ public void CustomOnAuthenticationFailure(Exception exception){
}
private String determineFailureUrl(AuthenticationException exception) {
- // 默认设置登录错误页面为/login
- defaultFailureUrl = StringUtils.hasLength(defaultFailureUrl) ? defaultFailureUrl : DEFAULT_FAILURE_URL;
-
+ String failureUrl = DEFAULT_FAILURE_URL;
Integer failureType = determineFailureType(exception).getType();
if (failureType != null) {
- defaultFailureUrl += defaultFailureUrl.lastIndexOf("?") > 0 ? "&" : "?" + "error=" + failureType;
+ failureUrl += (failureUrl.lastIndexOf("?") > 0 ? "&" : "?") + "error=" + failureType;
}
- return defaultFailureUrl;
+ return failureUrl;
}
private LoginError determineFailureType(AuthenticationException exception) {
- if (exception.getMessage() == "验证码为空"){
+ if ("验证码为空".equals(exception.getMessage())){
return LoginError.CAPTCHANOTFOUND;
- } else if (exception.getMessage() == "验证码过期") {
+ } else if ("验证码过期".equals(exception.getMessage())) {
return LoginError.CAPTCHAEXPIRED;
- } else if (exception.getMessage() == "验证码不正确") {
+ } else if ("验证码不正确".equals(exception.getMessage())) {
return LoginError.CAPTCHAERROR;
} else if (exception instanceof UsernameNotFoundException) {
return LoginError.USERNAMENOTFOUND;
@@ -87,14 +75,4 @@ private LoginError determineFailureType(AuthenticationException exception) {
return LoginError.FAILURE;
}
-
- public String getDefaultFailureUrl() {
- return defaultFailureUrl;
- }
-
- @Override
- public void setDefaultFailureUrl(String defaultFailureUrl) {
- super.setDefaultFailureUrl(defaultFailureUrl);
- }
-
}
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index 999df61..a0c1ca4 100755
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -1,43 +1,73 @@
spring:
datasource:
- type: com.zaxxer.hikari.HikariDataSource
- driver-class-name: com.mysql.cj.jdbc.Driver
- username: root
- password: QWE123qwe
- url: jdbc:mysql://localhost:13306/JavaSecLab?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
- druid:
- initial-size: 5
- min-idle: 5
- max-active: 20
- max-wait: 60000
- time-between-eviction-runs-millis: 60000
- min-evictable-idle-time-millis: 300000
- validation-query: SELECT 1 FROM DUAL
- test-while-idle: true
- test-on-borrow: false
- test-on-return: false
- pool-prepared-statements: true
- max-pool-prepared-statement-per-connection-size: 20
- filters: stat,log4j # wall 这里关闭sql防火墙
- connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
- remove-abandoned: true
- remove-abandoned-timeout: 1800
- log-abandoned: true
- web-stat-filter:
- enabled: true
- stat-view-servlet:
- enabled: true
- url-pattern: /druid/*
- # login-username: admin
- # login-password: admin
- reset-enable: false
- # 防火墙配置
-# wall:
-# config:
-# multi-statement-allow: false
+ primary:
+ type: com.alibaba.druid.pool.DruidDataSource
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:13306/JavaSecLab?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
+ username: root
+ password: QWE123qwe
+ druid:
+ initial-size: 5
+ min-idle: 5
+ max-active: 20
+ max-wait: 30000
+ validation-query: SELECT 1 FROM DUAL
+ test-while-idle: true
+ time-between-eviction-runs-millis: 60000
+ min-evictable-idle-time-millis: 300000
+ pool-prepared-statements: true
+ max-pool-prepared-statement-per-connection-size: 20
+ log-abandoned: true
+ remove-abandoned: true
+ secondary:
+ type: com.alibaba.druid.pool.DruidDataSource
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:13306/JavaSecLab?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
+ username: root
+ password: QWE123qwe
+ druid:
+ initial-size: 5
+ min-idle: 5
+ max-active: 20
+ max-wait: 30000
+ validation-query: SELECT 1 FROM DUAL
+ test-while-idle: true
+ time-between-eviction-runs-millis: 60000
+ min-evictable-idle-time-millis: 300000
+ pool-prepared-statements: true
+ max-pool-prepared-statement-per-connection-size: 20
+ log-abandoned: true
+ remove-abandoned: true
-# Hibernate 配置:将当前上下文策略设置为 Spring
-# jpa:
-# properties:
-# hibernate:
-# current_session_context_class: thread
\ No newline at end of file
+ jpa:
+ database-platform: org.hibernate.dialect.MySQLDialect
+ show-sql: true
+ hibernate:
+ ddl-auto: update
+ properties:
+ hibernate:
+ format_sql: true
+ session_factory_name: sessionFactory
+ session_factory_name_is_jndi: false
+ current_session_context_class: thread
+ transaction:
+ auto_close_session: true
+ connection:
+ provider_disables_autocommit: true
+ generate_statistics: true
+ jdbc:
+ time_zone: UTC
+ session:
+ events:
+ log:
+ LOG_QUERIES_SLOWER_THAN_MS: 0
+ flush_mode: AUTO
+ default_schema: JavaSecLab
+ default_catalog: JavaSecLab
+
+logging:
+ level:
+ root: INFO # 默认日志级别
+ com.alibaba.druid.pool: DEBUG # 启用 Druid 的 DEBUG 日志(排查数据库连接池问题时启用)
+ org.hibernate.SQL: DEBUG # 启用 Hibernate SQL 日志
+ org.hibernate.type.descriptor.sql.BasicBinder: TRACE # 启用 Hibernate 参数绑定日志
\ No newline at end of file
diff --git a/src/main/resources/application-docker.yml b/src/main/resources/application-docker.yml
index c0151f3..f7b4556 100755
--- a/src/main/resources/application-docker.yml
+++ b/src/main/resources/application-docker.yml
@@ -1,45 +1,73 @@
spring:
datasource:
- type: com.zaxxer.hikari.HikariDataSource
- driver-class-name: com.mysql.cj.jdbc.Driver
- username: root
- password: QWE123qwe
- url: jdbc:mysql://Container-MYSQL8:3306/JavaSecLab?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
-# url: jdbc:mysql://47.94.130.42:3306/JavaSecLab?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
- druid:
- initial-size: 5
- min-idle: 5
- max-active: 20
- max-wait: 60000
- time-between-eviction-runs-millis: 60000
- min-evictable-idle-time-millis: 300000
- validation-query: SELECT 1 FROM DUAL
- test-while-idle: true
- test-on-borrow: false
- test-on-return: false
- pool-prepared-statements: true
- max-pool-prepared-statement-per-connection-size: 20
- filters: stat,log4j # wall 这里关闭sql防火墙
- connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
- remove-abandoned: true
- remove-abandoned-timeout: 1800
- log-abandoned: true
- web-stat-filter:
- enabled: true
- stat-view-servlet:
- enabled: true
- url-pattern: /druid/*
- # login-username: admin
- # login-password: admin
- reset-enable: false
- # 防火墙配置
- # wall:
- # config:
- # multi-statement-allow: false
+ primary:
+ type: com.alibaba.druid.pool.DruidDataSource
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://mysql:3306/JavaSecLab?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
+ username: root
+ password: QWE123qwe
+ druid:
+ initial-size: 5
+ min-idle: 5
+ max-active: 20
+ max-wait: 30000
+ validation-query: SELECT 1 FROM DUAL
+ test-while-idle: true
+ time-between-eviction-runs-millis: 60000
+ min-evictable-idle-time-millis: 300000
+ pool-prepared-statements: true
+ max-pool-prepared-statement-per-connection-size: 20
+ log-abandoned: true
+ remove-abandoned: true
+ secondary:
+ type: com.alibaba.druid.pool.DruidDataSource
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://mysql:3306/JavaSecLab?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
+ username: root
+ password: QWE123qwe
+ druid:
+ initial-size: 5
+ min-idle: 5
+ max-active: 20
+ max-wait: 30000
+ validation-query: SELECT 1 FROM DUAL
+ test-while-idle: true
+ time-between-eviction-runs-millis: 60000
+ min-evictable-idle-time-millis: 300000
+ pool-prepared-statements: true
+ max-pool-prepared-statement-per-connection-size: 20
+ log-abandoned: true
+ remove-abandoned: true
jpa:
- hibernate:
- ddl-auto: none
- database: mysql
database-platform: org.hibernate.dialect.MySQLDialect
- show-sql: true
\ No newline at end of file
+ show-sql: true
+ hibernate:
+ ddl-auto: update
+ properties:
+ hibernate:
+ format_sql: true
+ session_factory_name: sessionFactory
+ session_factory_name_is_jndi: false
+ current_session_context_class: thread
+ transaction:
+ auto_close_session: true
+ connection:
+ provider_disables_autocommit: true
+ generate_statistics: true
+ jdbc:
+ time_zone: UTC
+ session:
+ events:
+ log:
+ LOG_QUERIES_SLOWER_THAN_MS: 0
+ flush_mode: AUTO
+ default_schema: JavaSecLab
+ default_catalog: JavaSecLab
+
+logging:
+ level:
+ root: INFO # 默认日志级别
+ com.alibaba.druid.pool: DEBUG # 启用 Druid 的 DEBUG 日志(排查数据库连接池问题时启用)
+ org.hibernate.SQL: DEBUG # 启用 Hibernate SQL 日志
+ org.hibernate.type.descriptor.sql.BasicBinder: TRACE # 启用 Hibernate 参数绑定日志
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 1c07fe4..facdf93 100755
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,13 +1,12 @@
-# Tomcat
server:
port: 80
-# servlet:
-# context-path: /javaseclab
spring:
# 环境 dev|docker
profiles:
- active: dev
+ active: docker
+ main:
+ allow-bean-definition-overriding: true
thymeleaf:
mode: LEGACYHTML5 #模板类型
cache: false #缓存
@@ -16,10 +15,7 @@ spring:
suffix: .html
mvc:
pathmatch:
- matching-strategy: ant_path_matcher #解决swaggerUI不匹配接口
-# view: # 设置JSP视图的前缀和后缀
-# prefix: /WEB-INF/jsp/
-# suffix: .jsp
+ matching-strategy: ANT_PATH_MATCHER #解决swaggerUI不匹配接口
swagger:
enable: true
@@ -44,6 +40,7 @@ management:
web:
exposure:
include: '*'
+ exclude:
base-path: /sys/actuator
logging:
@@ -52,11 +49,55 @@ logging:
# mybaits-plus配置
mybatis-plus:
configuration:
+ map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# MyBatis Mapper所对应的XML文件位置
- mapper-locations: /mapper/**/*Mapper.xml
+ mapper-locations: classpath*:/mapper/**/*.xml
global-config:
# 关闭MP3.0自带的banner
banner: false
+ db-config:
+ logic-delete-field: deleted
+ logic-delete-value: 1
+ logic-not-delete-value: 0
+ type-aliases-package: top.whgojp.modules.*.entity
-upload-path: ./upload
\ No newline at end of file
+folder:
+ upload: /tmp/upload
+ static: /tmp/static
+
+rsa:
+ private:
+ key: "-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDtttbAHKH8paAM
+bVChMxlQ900ZDIamD4k00Y0aSzIF03dXFkO3+1oFKYj3DDwa68G0XGOJOoRkpfYK
+LZdD45/2D32vcCb2tLzh9jFM79KG4+r+eflkQAMmCBAq5HZZG/Q9EsEfYgJ+YEvc
+57yKPH5NQnvsyl6909nvNzB07sbPJ6J5bsXkyUS4Smo1qPCQ6KhBmh2WREGFa1e0
+kb8w9RpySlOz81qn9gJoJZCn+DM+cK7tg24k0ZKMM1Awekr+1P5TA9Q9VGSYHBJd
+LMPVNKnSxrNLV0nlmBGX0uBkClxJCkycSyjubB4mlGULUuYIRRr+Cndj3jyJpuHm
+GKKD+4ujAgMBAAECggEASFW/YmU0G6OwoKdxBiR8+yjNsqYfoQ+QMlzjwZEJL0Gq
+insRbz5Spch+T6LO9WgxIPeOKFeAqvnfdThrU7LD3cXX+pc3nBHieiYG2YEOwJJB
+U199draN3rhMZyjvJG1/tEftMWYLGTanTxjLRAtlaZAmEqeADeaV5heWrLZuE+HH
+CaKbo7isy39vyqfGRa4ROe66N1pug8EHH7fPDqM4k8zMsLau80o6dc6HEKI0RB/H
+HXbdaomXhuMuEkuifrwnyLPyVrw4LPkPlLi4BIWJBYVYXjosV0kTCg5HSWbITQ0J
+jB48C3rM2SQuBrCiYu5tpluOx6Tsy5KrvKC04kRvGQKBgQD4PpQA49nweGQViMo8
+rRSTPvh6R3S9xglvrOWrDpMZAOKTKsFx1GgoJOYIbCWYo+L42hyVWL/pxWbq1lvf
+6UqPCoUqjP5U9TK5A2J8zJAo8erw3h5F/Bpz+DASluy0+3KhRJ5qrRAomhcZOtX+
+5gl19r2hY2KjdSSEPA2Qr0JEWQKBgQD1JAqg4FnMVi5/Apz6idgC6QW5scMSKgzY
+G+q2ehgGFvVA4Q8MezIFRBA/wsev+rh+WqWWuhJQKeWM49flIFQIGhlnoLTc3s/Z
+CtBLf3d73WTpEJdQdyqR0YDVQ3pGj1UTm7aS/Fs+FaAw7d6Y32gL5LVb0vQ/FKGy
+YKfGqi9AWwKBgHJUJ8fNKGdmmvmL+VA+ilZSTw/J7wsjtN7Y6yF/4eFHFhKfQ15Q
+a/PpIoRIgnwtJnBjy3xA1oosnvyS4tdZ0zvTpYb2ToAEOWsaEvbVI6On3wM12Q10
+UR6N9F3rYnLrx1xchPUuZV29sdutzDbL7RmGHMnCQwBzB/Fa0wiKnuNpAoGBAN8s
+QMDVfusYSpw2tNMiSxXbLusveng+8BKO18/ot5ZTsFOwkRK71X4VyPVDTqhXiT7/
+J2FhZOq2OdVaWGKwW9BEcnx1QjMSZgciYR9anFyX4haMlDUdSBQYt0FwfRFfzARd
+7olCVY7gAUaKR+zE9uRdAv7lvpbvIYZTmGq05O+hAoGAQdrN2pk4P7l/hax5F7yo
+hGUahXhPvN1OkI+772dFhjpQYxf02oKrdW/pNrTAoYyE9tCUUeZngUZ6SkN+TlJa
+ouK1o4xnmMD2YhHhzmxyn8wlLB8KopMzCQ8WaooivlJbyXQVp6bq9UFaeQW0NtIB
+tzMFGyiO+DvR4pO52uQLEBU=
+-----END PRIVATE KEY-----"
+
+jwt:
+ key: f3a4c6d5b9bfeff28b1f529b0840134bcd4183474e2d4a97c05615a134e4f4da
+
+#debug: true
\ No newline at end of file
diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt
index cf6c02b..79d4bf5 100755
--- a/src/main/resources/banner.txt
+++ b/src/main/resources/banner.txt
@@ -1,5 +1,8 @@
====================================================================================================================
-
- Powered By whgojp
+ __ _____ __ __
+ / /___ __ ______ _/ ___/___ _____/ / ____ _/ /_
+ __ / / __ `/ | / / __ `/\__ \/ _ \/ ___/ / / __ `/ __ \
+ / /_/ / /_/ /| |/ / /_/ /___/ / __/ /__/ /___/ /_/ / /_/ /
+ \____/\__,_/ |___/\__,_//____/\___/\___/_____/\__,_/_.___/
====================================================================================================================
\ No newline at end of file
diff --git a/src/main/resources/mapper/LogMapper.xml b/src/main/resources/mapper/LogMapper.xml
deleted file mode 100644
index 165a1b1..0000000
--- a/src/main/resources/mapper/LogMapper.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- logId,username,optionName,
- optionTerminal,optionIp,optionTime
-
-
diff --git a/src/main/resources/mapper/SqliMapper.xml b/src/main/resources/mapper/SqliMapper.xml
index b27d600..a977910 100644
--- a/src/main/resources/mapper/SqliMapper.xml
+++ b/src/main/resources/mapper/SqliMapper.xml
@@ -14,8 +14,8 @@
id,username,password
insert into sqli (id,username,password) values (#{id,jdbcType=INTEGER},#{username,jdbcType=VARCHAR},#{password,jdbcType=VARCHAR})
@@ -33,7 +33,7 @@
-
+
SELECT * FROM sqli
@@ -41,14 +41,14 @@
-
+
SELECT * FROM sqli
ORDER BY #{field}
-
+
SELECT * FROM sqli
diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml
index 0ca478e..e90f5bb 100644
--- a/src/main/resources/mapper/UserMapper.xml
+++ b/src/main/resources/mapper/UserMapper.xml
@@ -25,4 +25,9 @@
set password = #{password,jdbcType=VARCHAR}
where username = #{username,jdbcType=VARCHAR}
+
+ select *
+ from user
+ where username = #{username,jdbcType=VARCHAR}
+
diff --git a/src/main/resources/static/api/clear.json b/src/main/resources/static/api/clear.json
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/api/init.json b/src/main/resources/static/api/init.json
old mode 100644
new mode 100755
index 51d7e97..a1cd396
--- a/src/main/resources/static/api/init.json
+++ b/src/main/resources/static/api/init.json
@@ -28,13 +28,13 @@
"target": "_self",
"child": [
{
- "title": "漏洞环境",
+ "title": "漏洞场景",
"href": "xss/reflect/vul",
"icon": "iconfont icon-bug",
"target": "_self"
},
{
- "title": "安全环境",
+ "title": "安全场景",
"href": "xss/reflect/safe",
"icon": "iconfont icon-anquan",
"target": "_self"
@@ -74,13 +74,13 @@
"target": "_self",
"child": [
{
- "title": "漏洞环境",
+ "title": "漏洞场景",
"href": "sqli/jdbc/jdbcVul",
"icon": "iconfont icon-bug",
"target": "_self"
},
{
- "title": "安全代码",
+ "title": "安全场景",
"href": "sqli/jdbc/jdbcSafe",
"icon": "iconfont icon-anquan",
"target": "_self"
@@ -146,52 +146,77 @@
]
},
{
- "title": "RCE",
+ "title": "SSRF",
+ "href": "ssrf",
+ "icon": "iconfont icon-fuwuqingqiu",
+ "target": "_self"
+ },
+ {
+ "title": "XXE",
"href": "",
- "icon": "iconfont icon-minglingzhihang",
+ "icon": "iconfont icon-XML",
"target": "_self",
"child": [
{
- "title": "命令注入",
- "href": "command",
- "icon": "iconfont icon-minglingzhihang",
+ "title": "漏洞场景",
+ "href": "xxe/vul",
+ "icon": "iconfont icon-bug",
"target": "_self"
},
{
- "title": "代码注入",
- "href": "code",
- "icon": "iconfont icon-minglingzhihang",
+ "title": "安全场景",
+ "href": "xxe/safe",
+ "icon": "iconfont icon-anquan",
"target": "_self"
}
]
},
{
- "title": "SSRF",
- "href": "ssrf",
- "icon": "iconfont icon-fuwuqingqiu",
+ "title": "CSRF",
+ "href": "csrf",
+ "icon": "iconfont icon-kuazhanqingqiuweizao",
"target": "_self"
},
{
- "title": "XXE",
+ "title": "跨源安全",
"href": "",
- "icon": "iconfont icon-XML",
+ "icon": "iconfont icon-origin",
"target": "_self",
"child": [
{
- "title": "漏洞环境",
- "href": "xxe/vul",
- "icon": "iconfont icon-bug",
+ "title": "CORS",
+ "href": "crossorigin/cors",
+ "icon": "iconfont icon-cors",
"target": "_self"
},
{
- "title": "安全环境",
- "href": "xxe/safe",
- "icon": "iconfont icon-anquan",
+ "title": "JSONP",
+ "href": "crossorigin/jsonp",
+ "icon": "iconfont icon-JSON",
+ "target": "_self"
+ }
+ ]
+ },
+ {
+ "title": "RCE",
+ "href": "",
+ "icon": "iconfont icon-minglingzhihang",
+ "target": "_self",
+ "child": [
+ {
+ "title": "命令注入",
+ "href": "command",
+ "icon": "iconfont icon-minglingzhihang",
+ "target": "_self"
+ },
+ {
+ "title": "代码注入",
+ "href": "code",
+ "icon": "iconfont icon-minglingzhihang",
"target": "_self"
}
]
},
-
{
"title": "逻辑漏洞",
"href": "",
@@ -201,14 +226,54 @@
{
"title": "越权漏洞(IDOR)",
"href": "",
- "icon": "iconfont icon-redirect",
- "target": "_self"
+ "icon": "iconfont icon-quanxian",
+ "target": "_self",
+ "child": [
+ {
+ "title": "水平越权",
+ "href": "logic/idor/horizontal",
+ "icon": "iconfont icon-24gl-swapHorizontal3",
+ "target": "_self"
+ },
+ {
+ "title": "垂直越权",
+ "href": "logic/idor/vertical",
+ "icon": "iconfont icon-24gl-swapVertical3",
+ "target": "_self"
+ }
+ ]
},
{
"title": "支付漏洞",
- "href": "",
- "icon": "iconfont icon-redirect",
+ "href": "logic/pay",
+ "icon": "iconfont icon-zhifu",
+ "target": "_self"
+ },
+ {
+ "title": "并发安全",
+ "href": "logic/concurrent",
+ "icon": "iconfont icon-gaobingfa",
"target": "_self"
+ },
+ {
+ "title": "验证码安全",
+ "href": "",
+ "icon": "iconfont icon-yanzhengma1",
+ "target": "_self",
+ "child": [
+ {
+ "title": "图形验证码",
+ "href": "logic/captcha/graphic",
+ "icon": "iconfont icon-yanzhengma",
+ "target": "_self"
+ },
+ {
+ "title": "短信验证码",
+ "href": "logic/captcha/sms",
+ "icon": "iconfont icon-duanxin",
+ "target": "_self"
+ }
+ ]
}
]
},
@@ -225,13 +290,13 @@
"target": "_self",
"child": [
{
- "title": "漏洞环境",
+ "title": "漏洞场景",
"href": "other/URLRedirect/vul",
"icon": "iconfont icon-bug",
"target": "_self"
},
{
- "title": "安全环境",
+ "title": "安全场景",
"href": "other/URLRedirect/safe",
"icon": "iconfont icon-anquan",
"target": "_self"
@@ -245,30 +310,16 @@
"target": "_self"
},
{
- "title": "跨站请求伪造",
- "href": "other/csrf",
- "icon": "iconfont icon-kuazhanqingqiuweizao",
+ "title": "Dos攻击",
+ "href": "other/dos",
+ "icon": "iconfont icon-DOSgongji",
"target": "_self"
},
{
- "title": "跨源安全问题",
- "href": "",
- "icon": "iconfont icon-origin",
- "target": "_self",
- "child": [
- {
- "title": "CORS",
- "href": "other/CrossOrigin/cors",
- "icon": "iconfont icon-cors",
- "target": "_self"
- },
- {
- "title": "JSONP",
- "href": "other/CrossOrigin/jsonp",
- "icon": "iconfont icon-JSON",
- "target": "_self"
- }
- ]
+ "title": "XPATH注入",
+ "href": "other/xpath",
+ "icon": "iconfont icon-XPath",
+ "target": "_self"
}
]
},
@@ -303,7 +354,40 @@
"target": "_self"
}
]
+ },
+ {
+ "title": "登录对抗",
+ "href": "",
+ "icon": "iconfont icon-denglukuang",
+ "target": "_self",
+ "child": [
+ {
+ "title": "账号安全",
+ "href": "/loginconfront/account",
+ "icon": "iconfont icon-zhanghao",
+ "target": "_self"
+ },
+ {
+ "title": "登录绕过",
+ "href": "/loginconfront/bypass",
+ "icon": "iconfont icon-mimazhaohui",
+ "target": "_self"
+ },
+ {
+ "title": "JS逆向",
+ "href": "/loginconfront/reverse",
+ "icon": "iconfont icon-JS",
+ "target": "_self"
+ },
+ {
+ "title": "凭证安全",
+ "href": "/loginconfront/credential",
+ "icon": "iconfont icon-quanxianweizao",
+ "target": "_self"
+ }
+ ]
}
+
]
},
{
@@ -394,7 +478,8 @@
}
]
}
+
]
}
]
-}
\ No newline at end of file
+}
diff --git a/src/main/resources/static/api/menus.json b/src/main/resources/static/api/menus.json
deleted file mode 100644
index e14d00e..0000000
--- a/src/main/resources/static/api/menus.json
+++ /dev/null
@@ -1,254 +0,0 @@
-{
- "code": 0,
- "msg": "",
- "count": 19,
- "data": [
- {
- "authorityId": 1,
- "authorityName": "系统管理",
- "orderNumber": 1,
- "menuUrl": null,
- "menuIcon": "layui-icon-set",
- "createTime": "2018/06/29 11:05:41",
- "authority": null,
- "checked": 0,
- "updateTime": "2018/07/13 09:13:42",
- "isMenu": 0,
- "parentId": -1
- },
- {
- "authorityId": 2,
- "authorityName": "用户管理",
- "orderNumber": 2,
- "menuUrl": "system/user",
- "menuIcon": null,
- "createTime": "2018/06/29 11:05:41",
- "authority": null,
- "checked": 0,
- "updateTime": "2018/07/13 09:13:42",
- "isMenu": 0,
- "parentId": 1
- },
- {
- "authorityId": 3,
- "authorityName": "查询用户",
- "orderNumber": 3,
- "menuUrl": "",
- "menuIcon": "",
- "createTime": "2018/07/21 13:54:16",
- "authority": "user:view",
- "checked": 0,
- "updateTime": "2018/07/21 13:54:16",
- "isMenu": 1,
- "parentId": 2
- },
- {
- "authorityId": 4,
- "authorityName": "添加用户",
- "orderNumber": 4,
- "menuUrl": null,
- "menuIcon": null,
- "createTime": "2018/06/29 11:05:41",
- "authority": "user:add",
- "checked": 0,
- "updateTime": "2018/07/13 09:13:42",
- "isMenu": 1,
- "parentId": 2
- },
- {
- "authorityId": 5,
- "authorityName": "修改用户",
- "orderNumber": 5,
- "menuUrl": null,
- "menuIcon": null,
- "createTime": "2018/06/29 11:05:41",
- "authority": "user:edit",
- "checked": 0,
- "updateTime": "2018/07/13 09:13:42",
- "isMenu": 1,
- "parentId": 2
- },
- {
- "authorityId": 6,
- "authorityName": "删除用户",
- "orderNumber": 6,
- "menuUrl": null,
- "menuIcon": null,
- "createTime": "2018/06/29 11:05:41",
- "authority": "user:delete",
- "checked": 0,
- "updateTime": "2018/07/13 09:13:42",
- "isMenu": 1,
- "parentId": 2
- },
- {
- "authorityId": 7,
- "authorityName": "角色管理",
- "orderNumber": 7,
- "menuUrl": "system/role",
- "menuIcon": null,
- "createTime": "2018/06/29 11:05:41",
- "authority": null,
- "checked": 0,
- "updateTime": "2018/07/13 09:13:42",
- "isMenu": 0,
- "parentId": 1
- },
- {
- "authorityId": 8,
- "authorityName": "查询角色",
- "orderNumber": 8,
- "menuUrl": "",
- "menuIcon": "",
- "createTime": "2018/07/21 13:54:59",
- "authority": "role:view",
- "checked": 0,
- "updateTime": "2018/07/21 13:54:58",
- "isMenu": 1,
- "parentId": 7
- },
- {
- "authorityId": 9,
- "authorityName": "添加角色",
- "orderNumber": 9,
- "menuUrl": "",
- "menuIcon": "",
- "createTime": "2018/06/29 11:05:41",
- "authority": "role:add",
- "checked": 0,
- "updateTime": "2018/07/13 09:13:42",
- "isMenu": 1,
- "parentId": 7
- },
- {
- "authorityId": 10,
- "authorityName": "修改角色",
- "orderNumber": 10,
- "menuUrl": "",
- "menuIcon": "",
- "createTime": "2018/06/29 11:05:41",
- "authority": "role:edit",
- "checked": 0,
- "updateTime": "2018/07/13 09:13:42",
- "isMenu": 1,
- "parentId": 7
- },
- {
- "authorityId": 11,
- "authorityName": "删除角色",
- "orderNumber": 11,
- "menuUrl": "",
- "menuIcon": "",
- "createTime": "2018/06/29 11:05:41",
- "authority": "role:delete",
- "checked": 0,
- "updateTime": "2018/07/13 09:13:42",
- "isMenu": 1,
- "parentId": 7
- },
- {
- "authorityId": 12,
- "authorityName": "角色权限管理",
- "orderNumber": 12,
- "menuUrl": "",
- "menuIcon": "",
- "createTime": "2018/06/29 11:05:41",
- "authority": "role:auth",
- "checked": 0,
- "updateTime": "2018/07/13 15:27:18",
- "isMenu": 1,
- "parentId": 7
- },
- {
- "authorityId": 13,
- "authorityName": "权限管理",
- "orderNumber": 13,
- "menuUrl": "system/authorities",
- "menuIcon": null,
- "createTime": "2018/06/29 11:05:41",
- "authority": null,
- "checked": 0,
- "updateTime": "2018/07/13 15:45:13",
- "isMenu": 0,
- "parentId": 1
- },
- {
- "authorityId": 14,
- "authorityName": "查询权限",
- "orderNumber": 14,
- "menuUrl": "",
- "menuIcon": "",
- "createTime": "2018/07/21 13:55:57",
- "authority": "authorities:view",
- "checked": 0,
- "updateTime": "2018/07/21 13:55:56",
- "isMenu": 1,
- "parentId": 13
- },
- {
- "authorityId": 15,
- "authorityName": "添加权限",
- "orderNumber": 15,
- "menuUrl": "",
- "menuIcon": "",
- "createTime": "2018/06/29 11:05:41",
- "authority": "authorities:add",
- "checked": 0,
- "updateTime": "2018/06/29 11:05:41",
- "isMenu": 1,
- "parentId": 13
- },
- {
- "authorityId": 16,
- "authorityName": "修改权限",
- "orderNumber": 16,
- "menuUrl": "",
- "menuIcon": "",
- "createTime": "2018/07/13 09:13:42",
- "authority": "authorities:edit",
- "checked": 0,
- "updateTime": "2018/07/13 09:13:42",
- "isMenu": 1,
- "parentId": 13
- },
- {
- "authorityId": 17,
- "authorityName": "删除权限",
- "orderNumber": 17,
- "menuUrl": "",
- "menuIcon": "",
- "createTime": "2018/06/29 11:05:41",
- "authority": "authorities:delete",
- "checked": 0,
- "updateTime": "2018/06/29 11:05:41",
- "isMenu": 1,
- "parentId": 13
- },
- {
- "authorityId": 18,
- "authorityName": "登录日志",
- "orderNumber": 18,
- "menuUrl": "system/loginRecord",
- "menuIcon": null,
- "createTime": "2018/06/29 11:05:41",
- "authority": null,
- "checked": 0,
- "updateTime": "2018/06/29 11:05:41",
- "isMenu": 0,
- "parentId": 1
- },
- {
- "authorityId": 19,
- "authorityName": "查询登录日志",
- "orderNumber": 19,
- "menuUrl": "",
- "menuIcon": "",
- "createTime": "2018/07/21 13:56:43",
- "authority": "loginRecord:view",
- "checked": 0,
- "updateTime": "2018/07/21 13:56:43",
- "isMenu": 1,
- "parentId": 18
- }
- ]
-}
\ No newline at end of file
diff --git a/src/main/resources/static/api/table.json b/src/main/resources/static/api/table.json
deleted file mode 100644
index 7bda61b..0000000
--- a/src/main/resources/static/api/table.json
+++ /dev/null
@@ -1,127 +0,0 @@
-{
- "code": 0,
- "msg": "",
- "count": 1000,
- "data": [
- {
- "id": 10000,
- "username": "user-0",
- "sex": "女",
- "city": "城市-0",
- "sign": "签名-0",
- "experience": 255,
- "logins": 24,
- "wealth": 82830700,
- "classify": "作家",
- "score": 57
- },
- {
- "id": 10001,
- "username": "user-1",
- "sex": "男",
- "city": "城市-1",
- "sign": "签名-1",
- "experience": 884,
- "logins": 58,
- "wealth": 64928690,
- "classify": "词人",
- "score": 27
- },
- {
- "id": 10002,
- "username": "user-2",
- "sex": "女",
- "city": "城市-2",
- "sign": "签名-2",
- "experience": 650,
- "logins": 77,
- "wealth": 6298078,
- "classify": "酱油",
- "score": 31
- },
- {
- "id": 10003,
- "username": "user-3",
- "sex": "女",
- "city": "城市-3",
- "sign": "签名-3",
- "experience": 362,
- "logins": 157,
- "wealth": 37117017,
- "classify": "诗人",
- "score": 68
- },
- {
- "id": 10004,
- "username": "user-4",
- "sex": "男",
- "city": "城市-4",
- "sign": "签名-4",
- "experience": 807,
- "logins": 51,
- "wealth": 76263262,
- "classify": "作家",
- "score": 6
- },
- {
- "id": 10005,
- "username": "user-5",
- "sex": "女",
- "city": "城市-5",
- "sign": "签名-5",
- "experience": 173,
- "logins": 68,
- "wealth": 60344147,
- "classify": "作家",
- "score": 87
- },
- {
- "id": 10006,
- "username": "user-6",
- "sex": "女",
- "city": "城市-6",
- "sign": "签名-6",
- "experience": 982,
- "logins": 37,
- "wealth": 57768166,
- "classify": "作家",
- "score": 34
- },
- {
- "id": 10007,
- "username": "user-7",
- "sex": "男",
- "city": "城市-7",
- "sign": "签名-7",
- "experience": 727,
- "logins": 150,
- "wealth": 82030578,
- "classify": "作家",
- "score": 28
- },
- {
- "id": 10008,
- "username": "user-8",
- "sex": "男",
- "city": "城市-8",
- "sign": "签名-8",
- "experience": 951,
- "logins": 133,
- "wealth": 16503371,
- "classify": "词人",
- "score": 14
- },
- {
- "id": 10009,
- "username": "user-9",
- "sex": "女",
- "city": "城市-9",
- "sign": "签名-9",
- "experience": 484,
- "logins": 25,
- "wealth": 86801934,
- "classify": "词人",
- "score": 75
- }
- ]
-}
\ No newline at end of file
diff --git a/src/main/resources/static/api/tableSelect.json b/src/main/resources/static/api/tableSelect.json
deleted file mode 100644
index 37fb0ed..0000000
--- a/src/main/resources/static/api/tableSelect.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "code": 0,
- "msg": "",
- "count": 16,
- "data": [
- { "id":"001", "username":"张玉林", "sex":"女" },
- { "id":"002", "username":"刘晓军", "sex":"男" },
- { "id":"003", "username":"张恒", "sex":"男" },
- { "id":"004", "username":"朱一", "sex":"男" },
- { "id":"005", "username":"刘佳能", "sex":"女" },
- { "id":"006", "username":"晓梅", "sex":"女" },
- { "id":"007", "username":"马冬梅", "sex":"女" },
- { "id":"008", "username":"刘晓庆", "sex":"女" },
- { "id":"009", "username":"刘晓庆", "sex":"女" },
- { "id":"010", "username":"刘晓庆", "sex":"女" },
- { "id":"011", "username":"刘晓庆", "sex":"女" },
- { "id":"012", "username":"刘晓庆", "sex":"女" },
- { "id":"013", "username":"刘晓庆", "sex":"女" },
- { "id":"014", "username":"刘晓庆", "sex":"女" },
- { "id":"015", "username":"刘晓庆", "sex":"女" },
- { "id":"016", "username":"刘晓庆", "sex":"女" }
- ]
-}
\ No newline at end of file
diff --git a/src/main/resources/static/api/upload.json b/src/main/resources/static/api/upload.json
deleted file mode 100644
index 691902d..0000000
--- a/src/main/resources/static/api/upload.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "code": 1,
- "msg": "上传成功",
- "data": {
- "url": [
- "../images/logo.png",
- "../images/captcha.jpg"
- ]
- }
-}
diff --git a/src/main/resources/static/css/juejin.css b/src/main/resources/static/css/juejin.css
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/css/juejinsafe.css b/src/main/resources/static/css/juejinsafe.css
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/css/layuimini.css b/src/main/resources/static/css/layuimini.css
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/css/public.css b/src/main/resources/static/css/public.css
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/css/themes/default.css b/src/main/resources/static/css/themes/default.css
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/images/bg.jpg b/src/main/resources/static/images/bg.jpg
deleted file mode 100644
index 82b853e..0000000
Binary files a/src/main/resources/static/images/bg.jpg and /dev/null differ
diff --git a/src/main/resources/static/images/home.png b/src/main/resources/static/images/home.png
deleted file mode 100644
index 348ff27..0000000
Binary files a/src/main/resources/static/images/home.png and /dev/null differ
diff --git a/src/main/resources/static/images/icon-login.png b/src/main/resources/static/images/icon-login.png
deleted file mode 100644
index 1db2f96..0000000
Binary files a/src/main/resources/static/images/icon-login.png and /dev/null differ
diff --git a/src/main/resources/static/images/loginbg.png b/src/main/resources/static/images/loginbg.png
deleted file mode 100644
index 675c74b..0000000
Binary files a/src/main/resources/static/images/loginbg.png and /dev/null differ
diff --git a/src/main/resources/static/images/vul/components/deserialize.jpg b/src/main/resources/static/images/vul/components/deserialize.jpg
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/images/vul/components/fastjson.png b/src/main/resources/static/images/vul/components/fastjson.png
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/images/vul/components/log4j.png b/src/main/resources/static/images/vul/components/log4j.png
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/images/vul/components/shiro.png b/src/main/resources/static/images/vul/components/shiro.png
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/images/vul/crossOrigin/sameOrign.png b/src/main/resources/static/images/vul/crossOrigin/sameOrign.png
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/images/vul/csrf/csrf.png b/src/main/resources/static/images/vul/csrf/csrf.png
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/images/vul/dos/dos.jpeg b/src/main/resources/static/images/vul/dos/dos.jpeg
new file mode 100755
index 0000000..aeb69af
Binary files /dev/null and b/src/main/resources/static/images/vul/dos/dos.jpeg differ
diff --git a/src/main/resources/static/images/vul/idor/idor.png b/src/main/resources/static/images/vul/idor/idor.png
new file mode 100755
index 0000000..c534a44
Binary files /dev/null and b/src/main/resources/static/images/vul/idor/idor.png differ
diff --git a/src/main/resources/static/images/vul/jdbc/jdbc.png b/src/main/resources/static/images/vul/jdbc/jdbc.png
new file mode 100755
index 0000000..a4cf54f
Binary files /dev/null and b/src/main/resources/static/images/vul/jdbc/jdbc.png differ
diff --git a/src/main/resources/static/images/vul/memshell/filter.png b/src/main/resources/static/images/vul/memshell/filter.png
new file mode 100755
index 0000000..8b13789
--- /dev/null
+++ b/src/main/resources/static/images/vul/memshell/filter.png
@@ -0,0 +1 @@
+
diff --git a/src/main/resources/static/images/vul/memshell/listener.png b/src/main/resources/static/images/vul/memshell/listener.png
new file mode 100755
index 0000000..8b13789
--- /dev/null
+++ b/src/main/resources/static/images/vul/memshell/listener.png
@@ -0,0 +1 @@
+
diff --git a/src/main/resources/static/images/vul/memshell/servlet.png b/src/main/resources/static/images/vul/memshell/servlet.png
new file mode 100755
index 0000000..8b13789
--- /dev/null
+++ b/src/main/resources/static/images/vul/memshell/servlet.png
@@ -0,0 +1 @@
+
diff --git a/src/main/resources/static/images/vul/ssrf/ssrf.jpg b/src/main/resources/static/images/vul/ssrf/ssrf.jpg
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/images/vul/xss/dom.png b/src/main/resources/static/images/vul/xss/dom.png
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/images/vul/xss/reflect.png b/src/main/resources/static/images/vul/xss/reflect.png
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/images/vul/xss/store.jpg b/src/main/resources/static/images/vul/xss/store.jpg
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/images/vul/xxe/xxe.png b/src/main/resources/static/images/vul/xxe/xxe.png
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/hackcookie.js b/src/main/resources/static/js/hackcookie.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/header.js b/src/main/resources/static/js/header.js
old mode 100644
new mode 100755
index 0407df9..e7f3afb
--- a/src/main/resources/static/js/header.js
+++ b/src/main/resources/static/js/header.js
@@ -24,4 +24,4 @@ document.writeln("");
document.writeln("");
document.writeln("");
document.writeln("");
-document.writeln("");
+document.writeln("");
diff --git a/src/main/resources/static/js/jquery.request.js b/src/main/resources/static/js/jquery.request.js
old mode 100644
new mode 100755
index f4260aa..8002161
--- a/src/main/resources/static/js/jquery.request.js
+++ b/src/main/resources/static/js/jquery.request.js
@@ -42,7 +42,10 @@ function request (url, method, data = {}, contentType, back){
error: error,
headers: xhr.getAllResponseHeaders()
});
- return typeof back === "function" && back(null);
+ var message = xhr.responseJSON && xhr.responseJSON.msg
+ ? xhr.responseJSON.msg
+ : "请求失败:" + (xhr.status || status) + " " + (error || xhr.statusText || "");
+ return typeof back === "function" && back({code: 1, msg: message});
}
});
};
@@ -57,4 +60,3 @@ function postAjaxRequst (url, data, callBack) {
return typeof callBack == "function" && callBack(res)
})
};
-
diff --git a/src/main/resources/static/js/jsencrypt.min.js b/src/main/resources/static/js/jsencrypt.min.js
new file mode 100755
index 0000000..c2b6b10
--- /dev/null
+++ b/src/main/resources/static/js/jsencrypt.min.js
@@ -0,0 +1,1540 @@
+/*! For license information please see jsencrypt.min.js.LICENSE.txt */
+!function (t, e) {
+ "object" == typeof exports && "object" == typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define([], e) : "object" == typeof exports ? exports.JSEncrypt = e() : t.JSEncrypt = e()
+}(window, (function () {
+ return (() => {
+ "use strict";
+ var t = {
+ 771: (t, e, i) => {
+ function r(t) {
+ return "0123456789abcdefghijklmnopqrstuvwxyz".charAt(t)
+ }
+
+ function n(t, e) {
+ return t & e
+ }
+
+ function s(t, e) {
+ return t | e
+ }
+
+ function o(t, e) {
+ return t ^ e
+ }
+
+ function h(t, e) {
+ return t & ~e
+ }
+
+ function a(t) {
+ if (0 == t) return -1;
+ var e = 0;
+ return 0 == (65535 & t) && (t >>= 16, e += 16), 0 == (255 & t) && (t >>= 8, e += 8), 0 == (15 & t) && (t >>= 4, e += 4), 0 == (3 & t) && (t >>= 2, e += 2), 0 == (1 & t) && ++e, e
+ }
+
+ function u(t) {
+ for (var e = 0; 0 != t;) t &= t - 1, ++e;
+ return e
+ }
+
+ i.d(e, {default: () => nt});
+ var c, f = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ function l(t) {
+ var e, i, r = "";
+ for (e = 0; e + 3 <= t.length; e += 3) i = parseInt(t.substring(e, e + 3), 16), r += f.charAt(i >> 6) + f.charAt(63 & i);
+ for (e + 1 == t.length ? (i = parseInt(t.substring(e, e + 1), 16), r += f.charAt(i << 2)) : e + 2 == t.length && (i = parseInt(t.substring(e, e + 2), 16), r += f.charAt(i >> 2) + f.charAt((3 & i) << 4)); (3 & r.length) > 0;) r += "=";
+ return r
+ }
+
+ function p(t) {
+ var e, i = "", n = 0, s = 0;
+ for (e = 0; e < t.length && "=" != t.charAt(e); ++e) {
+ var o = f.indexOf(t.charAt(e));
+ o < 0 || (0 == n ? (i += r(o >> 2), s = 3 & o, n = 1) : 1 == n ? (i += r(s << 2 | o >> 4), s = 15 & o, n = 2) : 2 == n ? (i += r(s), i += r(o >> 2), s = 3 & o, n = 3) : (i += r(s << 2 | o >> 4), i += r(15 & o), n = 0))
+ }
+ return 1 == n && (i += r(s << 2)), i
+ }
+
+ var g, d = {
+ decode: function (t) {
+ var e;
+ if (void 0 === g) {
+ var i = "= \f\n\r\t \u2028\u2029";
+ for (g = Object.create(null), e = 0; e < 64; ++e) g["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(e)] = e;
+ for (g["-"] = 62, g._ = 63, e = 0; e < i.length; ++e) g[i.charAt(e)] = -1
+ }
+ var r = [], n = 0, s = 0;
+ for (e = 0; e < t.length; ++e) {
+ var o = t.charAt(e);
+ if ("=" == o) break;
+ if (-1 != (o = g[o])) {
+ if (void 0 === o) throw new Error("Illegal character at offset " + e);
+ n |= o, ++s >= 4 ? (r[r.length] = n >> 16, r[r.length] = n >> 8 & 255, r[r.length] = 255 & n, n = 0, s = 0) : n <<= 6
+ }
+ }
+ switch (s) {
+ case 1:
+ throw new Error("Base64 encoding incomplete: at least 2 bits missing");
+ case 2:
+ r[r.length] = n >> 10;
+ break;
+ case 3:
+ r[r.length] = n >> 16, r[r.length] = n >> 8 & 255
+ }
+ return r
+ },
+ re: /-----BEGIN [^-]+-----([A-Za-z0-9+\/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+\/=\s]+)====/,
+ unarmor: function (t) {
+ var e = d.re.exec(t);
+ if (e) if (e[1]) t = e[1]; else {
+ if (!e[2]) throw new Error("RegExp out of sync");
+ t = e[2]
+ }
+ return d.decode(t)
+ }
+ }, v = 1e13, m = function () {
+ function t(t) {
+ this.buf = [+t || 0]
+ }
+
+ return t.prototype.mulAdd = function (t, e) {
+ var i, r, n = this.buf, s = n.length;
+ for (i = 0; i < s; ++i) (r = n[i] * t + e) < v ? e = 0 : r -= (e = 0 | r / v) * v, n[i] = r;
+ e > 0 && (n[i] = e)
+ }, t.prototype.sub = function (t) {
+ var e, i, r = this.buf, n = r.length;
+ for (e = 0; e < n; ++e) (i = r[e] - t) < 0 ? (i += v, t = 1) : t = 0, r[e] = i;
+ for (; 0 === r[r.length - 1];) r.pop()
+ }, t.prototype.toString = function (t) {
+ if (10 != (t || 10)) throw new Error("only base 10 is supported");
+ for (var e = this.buf, i = e[e.length - 1].toString(), r = e.length - 2; r >= 0; --r) i += (v + e[r]).toString().substring(1);
+ return i
+ }, t.prototype.valueOf = function () {
+ for (var t = this.buf, e = 0, i = t.length - 1; i >= 0; --i) e = e * v + t[i];
+ return e
+ }, t.prototype.simplify = function () {
+ var t = this.buf;
+ return 1 == t.length ? t[0] : this
+ }, t
+ }(),
+ y = /^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/,
+ b = /^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/;
+
+ function T(t, e) {
+ return t.length > e && (t = t.substring(0, e) + "…"), t
+ }
+
+ var S, E = function () {
+ function t(e, i) {
+ this.hexDigits = "0123456789ABCDEF", e instanceof t ? (this.enc = e.enc, this.pos = e.pos) : (this.enc = e, this.pos = i)
+ }
+
+ return t.prototype.get = function (t) {
+ if (void 0 === t && (t = this.pos++), t >= this.enc.length) throw new Error("Requesting byte offset " + t + " on a stream of length " + this.enc.length);
+ return "string" == typeof this.enc ? this.enc.charCodeAt(t) : this.enc[t]
+ }, t.prototype.hexByte = function (t) {
+ return this.hexDigits.charAt(t >> 4 & 15) + this.hexDigits.charAt(15 & t)
+ }, t.prototype.hexDump = function (t, e, i) {
+ for (var r = "", n = t; n < e; ++n) if (r += this.hexByte(this.get(n)), !0 !== i) switch (15 & n) {
+ case 7:
+ r += " ";
+ break;
+ case 15:
+ r += "\n";
+ break;
+ default:
+ r += " "
+ }
+ return r
+ }, t.prototype.isASCII = function (t, e) {
+ for (var i = t; i < e; ++i) {
+ var r = this.get(i);
+ if (r < 32 || r > 176) return !1
+ }
+ return !0
+ }, t.prototype.parseStringISO = function (t, e) {
+ for (var i = "", r = t; r < e; ++r) i += String.fromCharCode(this.get(r));
+ return i
+ }, t.prototype.parseStringUTF = function (t, e) {
+ for (var i = "", r = t; r < e;) {
+ var n = this.get(r++);
+ i += n < 128 ? String.fromCharCode(n) : n > 191 && n < 224 ? String.fromCharCode((31 & n) << 6 | 63 & this.get(r++)) : String.fromCharCode((15 & n) << 12 | (63 & this.get(r++)) << 6 | 63 & this.get(r++))
+ }
+ return i
+ }, t.prototype.parseStringBMP = function (t, e) {
+ for (var i, r, n = "", s = t; s < e;) i = this.get(s++), r = this.get(s++), n += String.fromCharCode(i << 8 | r);
+ return n
+ }, t.prototype.parseTime = function (t, e, i) {
+ var r = this.parseStringISO(t, e), n = (i ? y : b).exec(r);
+ return n ? (i && (n[1] = +n[1], n[1] += +n[1] < 70 ? 2e3 : 1900), r = n[1] + "-" + n[2] + "-" + n[3] + " " + n[4], n[5] && (r += ":" + n[5], n[6] && (r += ":" + n[6], n[7] && (r += "." + n[7]))), n[8] && (r += " UTC", "Z" != n[8] && (r += n[8], n[9] && (r += ":" + n[9]))), r) : "Unrecognized time: " + r
+ }, t.prototype.parseInteger = function (t, e) {
+ for (var i, r = this.get(t), n = r > 127, s = n ? 255 : 0, o = ""; r == s && ++t < e;) r = this.get(t);
+ if (0 == (i = e - t)) return n ? -1 : 0;
+ if (i > 4) {
+ for (o = r, i <<= 3; 0 == (128 & (+o ^ s));) o = +o << 1, --i;
+ o = "(" + i + " bit)\n"
+ }
+ n && (r -= 256);
+ for (var h = new m(r), a = t + 1; a < e; ++a) h.mulAdd(256, this.get(a));
+ return o + h.toString()
+ }, t.prototype.parseBitString = function (t, e, i) {
+ for (var r = this.get(t), n = "(" + ((e - t - 1 << 3) - r) + " bit)\n", s = "", o = t + 1; o < e; ++o) {
+ for (var h = this.get(o), a = o == e - 1 ? r : 0, u = 7; u >= a; --u) s += h >> u & 1 ? "1" : "0";
+ if (s.length > i) return n + T(s, i)
+ }
+ return n + s
+ }, t.prototype.parseOctetString = function (t, e, i) {
+ if (this.isASCII(t, e)) return T(this.parseStringISO(t, e), i);
+ var r = e - t, n = "(" + r + " byte)\n";
+ r > (i /= 2) && (e = t + i);
+ for (var s = t; s < e; ++s) n += this.hexByte(this.get(s));
+ return r > i && (n += "…"), n
+ }, t.prototype.parseOID = function (t, e, i) {
+ for (var r = "", n = new m, s = 0, o = t; o < e; ++o) {
+ var h = this.get(o);
+ if (n.mulAdd(128, 127 & h), s += 7, !(128 & h)) {
+ if ("" === r) if ((n = n.simplify()) instanceof m) n.sub(80), r = "2." + n.toString(); else {
+ var a = n < 80 ? n < 40 ? 0 : 1 : 2;
+ r = a + "." + (n - 40 * a)
+ } else r += "." + n.toString();
+ if (r.length > i) return T(r, i);
+ n = new m, s = 0
+ }
+ }
+ return s > 0 && (r += ".incomplete"), r
+ }, t
+ }(), w = function () {
+ function t(t, e, i, r, n) {
+ if (!(r instanceof D)) throw new Error("Invalid tag value.");
+ this.stream = t, this.header = e, this.length = i, this.tag = r, this.sub = n
+ }
+
+ return t.prototype.typeName = function () {
+ switch (this.tag.tagClass) {
+ case 0:
+ switch (this.tag.tagNumber) {
+ case 0:
+ return "EOC";
+ case 1:
+ return "BOOLEAN";
+ case 2:
+ return "INTEGER";
+ case 3:
+ return "BIT_STRING";
+ case 4:
+ return "OCTET_STRING";
+ case 5:
+ return "NULL";
+ case 6:
+ return "OBJECT_IDENTIFIER";
+ case 7:
+ return "ObjectDescriptor";
+ case 8:
+ return "EXTERNAL";
+ case 9:
+ return "REAL";
+ case 10:
+ return "ENUMERATED";
+ case 11:
+ return "EMBEDDED_PDV";
+ case 12:
+ return "UTF8String";
+ case 16:
+ return "SEQUENCE";
+ case 17:
+ return "SET";
+ case 18:
+ return "NumericString";
+ case 19:
+ return "PrintableString";
+ case 20:
+ return "TeletexString";
+ case 21:
+ return "VideotexString";
+ case 22:
+ return "IA5String";
+ case 23:
+ return "UTCTime";
+ case 24:
+ return "GeneralizedTime";
+ case 25:
+ return "GraphicString";
+ case 26:
+ return "VisibleString";
+ case 27:
+ return "GeneralString";
+ case 28:
+ return "UniversalString";
+ case 30:
+ return "BMPString"
+ }
+ return "Universal_" + this.tag.tagNumber.toString();
+ case 1:
+ return "Application_" + this.tag.tagNumber.toString();
+ case 2:
+ return "[" + this.tag.tagNumber.toString() + "]";
+ case 3:
+ return "Private_" + this.tag.tagNumber.toString()
+ }
+ }, t.prototype.content = function (t) {
+ if (void 0 === this.tag) return null;
+ void 0 === t && (t = 1 / 0);
+ var e = this.posContent(), i = Math.abs(this.length);
+ if (!this.tag.isUniversal()) return null !== this.sub ? "(" + this.sub.length + " elem)" : this.stream.parseOctetString(e, e + i, t);
+ switch (this.tag.tagNumber) {
+ case 1:
+ return 0 === this.stream.get(e) ? "false" : "true";
+ case 2:
+ return this.stream.parseInteger(e, e + i);
+ case 3:
+ return this.sub ? "(" + this.sub.length + " elem)" : this.stream.parseBitString(e, e + i, t);
+ case 4:
+ return this.sub ? "(" + this.sub.length + " elem)" : this.stream.parseOctetString(e, e + i, t);
+ case 6:
+ return this.stream.parseOID(e, e + i, t);
+ case 16:
+ case 17:
+ return null !== this.sub ? "(" + this.sub.length + " elem)" : "(no elem)";
+ case 12:
+ return T(this.stream.parseStringUTF(e, e + i), t);
+ case 18:
+ case 19:
+ case 20:
+ case 21:
+ case 22:
+ case 26:
+ return T(this.stream.parseStringISO(e, e + i), t);
+ case 30:
+ return T(this.stream.parseStringBMP(e, e + i), t);
+ case 23:
+ case 24:
+ return this.stream.parseTime(e, e + i, 23 == this.tag.tagNumber)
+ }
+ return null
+ }, t.prototype.toString = function () {
+ return this.typeName() + "@" + this.stream.pos + "[header:" + this.header + ",length:" + this.length + ",sub:" + (null === this.sub ? "null" : this.sub.length) + "]"
+ }, t.prototype.toPrettyString = function (t) {
+ void 0 === t && (t = "");
+ var e = t + this.typeName() + " @" + this.stream.pos;
+ if (this.length >= 0 && (e += "+"), e += this.length, this.tag.tagConstructed ? e += " (constructed)" : !this.tag.isUniversal() || 3 != this.tag.tagNumber && 4 != this.tag.tagNumber || null === this.sub || (e += " (encapsulates)"), e += "\n", null !== this.sub) {
+ t += " ";
+ for (var i = 0, r = this.sub.length; i < r; ++i) e += this.sub[i].toPrettyString(t)
+ }
+ return e
+ }, t.prototype.posStart = function () {
+ return this.stream.pos
+ }, t.prototype.posContent = function () {
+ return this.stream.pos + this.header
+ }, t.prototype.posEnd = function () {
+ return this.stream.pos + this.header + Math.abs(this.length)
+ }, t.prototype.toHexString = function () {
+ return this.stream.hexDump(this.posStart(), this.posEnd(), !0)
+ }, t.decodeLength = function (t) {
+ var e = t.get(), i = 127 & e;
+ if (i == e) return i;
+ if (i > 6) throw new Error("Length over 48 bits not supported at position " + (t.pos - 1));
+ if (0 === i) return null;
+ e = 0;
+ for (var r = 0; r < i; ++r) e = 256 * e + t.get();
+ return e
+ }, t.prototype.getHexStringValue = function () {
+ var t = this.toHexString(), e = 2 * this.header, i = 2 * this.length;
+ return t.substr(e, i)
+ }, t.decode = function (e) {
+ var i;
+ i = e instanceof E ? e : new E(e, 0);
+ var r = new E(i), n = new D(i), s = t.decodeLength(i), o = i.pos, h = o - r.pos, a = null,
+ u = function () {
+ var e = [];
+ if (null !== s) {
+ for (var r = o + s; i.pos < r;) e[e.length] = t.decode(i);
+ if (i.pos != r) throw new Error("Content size is not correct for container starting at offset " + o)
+ } else try {
+ for (; ;) {
+ var n = t.decode(i);
+ if (n.tag.isEOC()) break;
+ e[e.length] = n
+ }
+ s = o - i.pos
+ } catch (t) {
+ throw new Error("Exception while decoding undefined length content: " + t)
+ }
+ return e
+ };
+ if (n.tagConstructed) a = u(); else if (n.isUniversal() && (3 == n.tagNumber || 4 == n.tagNumber)) try {
+ if (3 == n.tagNumber && 0 != i.get()) throw new Error("BIT STRINGs with unused bits cannot encapsulate.");
+ a = u();
+ for (var c = 0; c < a.length; ++c) if (a[c].tag.isEOC()) throw new Error("EOC is not supposed to be actual content.")
+ } catch (t) {
+ a = null
+ }
+ if (null === a) {
+ if (null === s) throw new Error("We can't skip over an invalid tag with undefined length at offset " + o);
+ i.pos = o + Math.abs(s)
+ }
+ return new t(r, h, s, n, a)
+ }, t
+ }(), D = function () {
+ function t(t) {
+ var e = t.get();
+ if (this.tagClass = e >> 6, this.tagConstructed = 0 != (32 & e), this.tagNumber = 31 & e, 31 == this.tagNumber) {
+ var i = new m;
+ do {
+ e = t.get(), i.mulAdd(128, 127 & e)
+ } while (128 & e);
+ this.tagNumber = i.simplify()
+ }
+ }
+
+ return t.prototype.isUniversal = function () {
+ return 0 === this.tagClass
+ }, t.prototype.isEOC = function () {
+ return 0 === this.tagClass && 0 === this.tagNumber
+ }, t
+ }(),
+ x = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997],
+ R = (1 << 26) / x[x.length - 1], B = function () {
+ function t(t, e, i) {
+ null != t && ("number" == typeof t ? this.fromNumber(t, e, i) : null == e && "string" != typeof t ? this.fromString(t, 256) : this.fromString(t, e))
+ }
+
+ return t.prototype.toString = function (t) {
+ if (this.s < 0) return "-" + this.negate().toString(t);
+ var e;
+ if (16 == t) e = 4; else if (8 == t) e = 3; else if (2 == t) e = 1; else if (32 == t) e = 5; else {
+ if (4 != t) return this.toRadix(t);
+ e = 2
+ }
+ var i, n = (1 << e) - 1, s = !1, o = "", h = this.t, a = this.DB - h * this.DB % e;
+ if (h-- > 0) for (a < this.DB && (i = this[h] >> a) > 0 && (s = !0, o = r(i)); h >= 0;) a < e ? (i = (this[h] & (1 << a) - 1) << e - a, i |= this[--h] >> (a += this.DB - e)) : (i = this[h] >> (a -= e) & n, a <= 0 && (a += this.DB, --h)), i > 0 && (s = !0), s && (o += r(i));
+ return s ? o : "0"
+ }, t.prototype.negate = function () {
+ var e = N();
+ return t.ZERO.subTo(this, e), e
+ }, t.prototype.abs = function () {
+ return this.s < 0 ? this.negate() : this
+ }, t.prototype.compareTo = function (t) {
+ var e = this.s - t.s;
+ if (0 != e) return e;
+ var i = this.t;
+ if (0 != (e = i - t.t)) return this.s < 0 ? -e : e;
+ for (; --i >= 0;) if (0 != (e = this[i] - t[i])) return e;
+ return 0
+ }, t.prototype.bitLength = function () {
+ return this.t <= 0 ? 0 : this.DB * (this.t - 1) + F(this[this.t - 1] ^ this.s & this.DM)
+ }, t.prototype.mod = function (e) {
+ var i = N();
+ return this.abs().divRemTo(e, null, i), this.s < 0 && i.compareTo(t.ZERO) > 0 && e.subTo(i, i), i
+ }, t.prototype.modPowInt = function (t, e) {
+ var i;
+ return i = t < 256 || e.isEven() ? new A(e) : new V(e), this.exp(t, i)
+ }, t.prototype.clone = function () {
+ var t = N();
+ return this.copyTo(t), t
+ }, t.prototype.intValue = function () {
+ if (this.s < 0) {
+ if (1 == this.t) return this[0] - this.DV;
+ if (0 == this.t) return -1
+ } else {
+ if (1 == this.t) return this[0];
+ if (0 == this.t) return 0
+ }
+ return (this[1] & (1 << 32 - this.DB) - 1) << this.DB | this[0]
+ }, t.prototype.byteValue = function () {
+ return 0 == this.t ? this.s : this[0] << 24 >> 24
+ }, t.prototype.shortValue = function () {
+ return 0 == this.t ? this.s : this[0] << 16 >> 16
+ }, t.prototype.signum = function () {
+ return this.s < 0 ? -1 : this.t <= 0 || 1 == this.t && this[0] <= 0 ? 0 : 1
+ }, t.prototype.toByteArray = function () {
+ var t = this.t, e = [];
+ e[0] = this.s;
+ var i, r = this.DB - t * this.DB % 8, n = 0;
+ if (t-- > 0) for (r < this.DB && (i = this[t] >> r) != (this.s & this.DM) >> r && (e[n++] = i | this.s << this.DB - r); t >= 0;) r < 8 ? (i = (this[t] & (1 << r) - 1) << 8 - r, i |= this[--t] >> (r += this.DB - 8)) : (i = this[t] >> (r -= 8) & 255, r <= 0 && (r += this.DB, --t)), 0 != (128 & i) && (i |= -256), 0 == n && (128 & this.s) != (128 & i) && ++n, (n > 0 || i != this.s) && (e[n++] = i);
+ return e
+ }, t.prototype.equals = function (t) {
+ return 0 == this.compareTo(t)
+ }, t.prototype.min = function (t) {
+ return this.compareTo(t) < 0 ? this : t
+ }, t.prototype.max = function (t) {
+ return this.compareTo(t) > 0 ? this : t
+ }, t.prototype.and = function (t) {
+ var e = N();
+ return this.bitwiseTo(t, n, e), e
+ }, t.prototype.or = function (t) {
+ var e = N();
+ return this.bitwiseTo(t, s, e), e
+ }, t.prototype.xor = function (t) {
+ var e = N();
+ return this.bitwiseTo(t, o, e), e
+ }, t.prototype.andNot = function (t) {
+ var e = N();
+ return this.bitwiseTo(t, h, e), e
+ }, t.prototype.not = function () {
+ for (var t = N(), e = 0; e < this.t; ++e) t[e] = this.DM & ~this[e];
+ return t.t = this.t, t.s = ~this.s, t
+ }, t.prototype.shiftLeft = function (t) {
+ var e = N();
+ return t < 0 ? this.rShiftTo(-t, e) : this.lShiftTo(t, e), e
+ }, t.prototype.shiftRight = function (t) {
+ var e = N();
+ return t < 0 ? this.lShiftTo(-t, e) : this.rShiftTo(t, e), e
+ }, t.prototype.getLowestSetBit = function () {
+ for (var t = 0; t < this.t; ++t) if (0 != this[t]) return t * this.DB + a(this[t]);
+ return this.s < 0 ? this.t * this.DB : -1
+ }, t.prototype.bitCount = function () {
+ for (var t = 0, e = this.s & this.DM, i = 0; i < this.t; ++i) t += u(this[i] ^ e);
+ return t
+ }, t.prototype.testBit = function (t) {
+ var e = Math.floor(t / this.DB);
+ return e >= this.t ? 0 != this.s : 0 != (this[e] & 1 << t % this.DB)
+ }, t.prototype.setBit = function (t) {
+ return this.changeBit(t, s)
+ }, t.prototype.clearBit = function (t) {
+ return this.changeBit(t, h)
+ }, t.prototype.flipBit = function (t) {
+ return this.changeBit(t, o)
+ }, t.prototype.add = function (t) {
+ var e = N();
+ return this.addTo(t, e), e
+ }, t.prototype.subtract = function (t) {
+ var e = N();
+ return this.subTo(t, e), e
+ }, t.prototype.multiply = function (t) {
+ var e = N();
+ return this.multiplyTo(t, e), e
+ }, t.prototype.divide = function (t) {
+ var e = N();
+ return this.divRemTo(t, e, null), e
+ }, t.prototype.remainder = function (t) {
+ var e = N();
+ return this.divRemTo(t, null, e), e
+ }, t.prototype.divideAndRemainder = function (t) {
+ var e = N(), i = N();
+ return this.divRemTo(t, e, i), [e, i]
+ }, t.prototype.modPow = function (t, e) {
+ var i, r, n = t.bitLength(), s = C(1);
+ if (n <= 0) return s;
+ i = n < 18 ? 1 : n < 48 ? 3 : n < 144 ? 4 : n < 768 ? 5 : 6, r = n < 8 ? new A(e) : e.isEven() ? new I(e) : new V(e);
+ var o = [], h = 3, a = i - 1, u = (1 << i) - 1;
+ if (o[1] = r.convert(this), i > 1) {
+ var c = N();
+ for (r.sqrTo(o[1], c); h <= u;) o[h] = N(), r.mulTo(c, o[h - 2], o[h]), h += 2
+ }
+ var f, l, p = t.t - 1, g = !0, d = N();
+ for (n = F(t[p]) - 1; p >= 0;) {
+ for (n >= a ? f = t[p] >> n - a & u : (f = (t[p] & (1 << n + 1) - 1) << a - n, p > 0 && (f |= t[p - 1] >> this.DB + n - a)), h = i; 0 == (1 & f);) f >>= 1, --h;
+ if ((n -= h) < 0 && (n += this.DB, --p), g) o[f].copyTo(s), g = !1; else {
+ for (; h > 1;) r.sqrTo(s, d), r.sqrTo(d, s), h -= 2;
+ h > 0 ? r.sqrTo(s, d) : (l = s, s = d, d = l), r.mulTo(d, o[f], s)
+ }
+ for (; p >= 0 && 0 == (t[p] & 1 << n);) r.sqrTo(s, d), l = s, s = d, d = l, --n < 0 && (n = this.DB - 1, --p)
+ }
+ return r.revert(s)
+ }, t.prototype.modInverse = function (e) {
+ var i = e.isEven();
+ if (this.isEven() && i || 0 == e.signum()) return t.ZERO;
+ for (var r = e.clone(), n = this.clone(), s = C(1), o = C(0), h = C(0), a = C(1); 0 != r.signum();) {
+ for (; r.isEven();) r.rShiftTo(1, r), i ? (s.isEven() && o.isEven() || (s.addTo(this, s), o.subTo(e, o)), s.rShiftTo(1, s)) : o.isEven() || o.subTo(e, o), o.rShiftTo(1, o);
+ for (; n.isEven();) n.rShiftTo(1, n), i ? (h.isEven() && a.isEven() || (h.addTo(this, h), a.subTo(e, a)), h.rShiftTo(1, h)) : a.isEven() || a.subTo(e, a), a.rShiftTo(1, a);
+ r.compareTo(n) >= 0 ? (r.subTo(n, r), i && s.subTo(h, s), o.subTo(a, o)) : (n.subTo(r, n), i && h.subTo(s, h), a.subTo(o, a))
+ }
+ return 0 != n.compareTo(t.ONE) ? t.ZERO : a.compareTo(e) >= 0 ? a.subtract(e) : a.signum() < 0 ? (a.addTo(e, a), a.signum() < 0 ? a.add(e) : a) : a
+ }, t.prototype.pow = function (t) {
+ return this.exp(t, new O)
+ }, t.prototype.gcd = function (t) {
+ var e = this.s < 0 ? this.negate() : this.clone(), i = t.s < 0 ? t.negate() : t.clone();
+ if (e.compareTo(i) < 0) {
+ var r = e;
+ e = i, i = r
+ }
+ var n = e.getLowestSetBit(), s = i.getLowestSetBit();
+ if (s < 0) return e;
+ for (n < s && (s = n), s > 0 && (e.rShiftTo(s, e), i.rShiftTo(s, i)); e.signum() > 0;) (n = e.getLowestSetBit()) > 0 && e.rShiftTo(n, e), (n = i.getLowestSetBit()) > 0 && i.rShiftTo(n, i), e.compareTo(i) >= 0 ? (e.subTo(i, e), e.rShiftTo(1, e)) : (i.subTo(e, i), i.rShiftTo(1, i));
+ return s > 0 && i.lShiftTo(s, i), i
+ }, t.prototype.isProbablePrime = function (t) {
+ var e, i = this.abs();
+ if (1 == i.t && i[0] <= x[x.length - 1]) {
+ for (e = 0; e < x.length; ++e) if (i[0] == x[e]) return !0;
+ return !1
+ }
+ if (i.isEven()) return !1;
+ for (e = 1; e < x.length;) {
+ for (var r = x[e], n = e + 1; n < x.length && r < R;) r *= x[n++];
+ for (r = i.modInt(r); e < n;) if (r % x[e++] == 0) return !1
+ }
+ return i.millerRabin(t)
+ }, t.prototype.copyTo = function (t) {
+ for (var e = this.t - 1; e >= 0; --e) t[e] = this[e];
+ t.t = this.t, t.s = this.s
+ }, t.prototype.fromInt = function (t) {
+ this.t = 1, this.s = t < 0 ? -1 : 0, t > 0 ? this[0] = t : t < -1 ? this[0] = t + this.DV : this.t = 0
+ }, t.prototype.fromString = function (e, i) {
+ var r;
+ if (16 == i) r = 4; else if (8 == i) r = 3; else if (256 == i) r = 8; else if (2 == i) r = 1; else if (32 == i) r = 5; else {
+ if (4 != i) return void this.fromRadix(e, i);
+ r = 2
+ }
+ this.t = 0, this.s = 0;
+ for (var n = e.length, s = !1, o = 0; --n >= 0;) {
+ var h = 8 == r ? 255 & +e[n] : H(e, n);
+ h < 0 ? "-" == e.charAt(n) && (s = !0) : (s = !1, 0 == o ? this[this.t++] = h : o + r > this.DB ? (this[this.t - 1] |= (h & (1 << this.DB - o) - 1) << o, this[this.t++] = h >> this.DB - o) : this[this.t - 1] |= h << o, (o += r) >= this.DB && (o -= this.DB))
+ }
+ 8 == r && 0 != (128 & +e[0]) && (this.s = -1, o > 0 && (this[this.t - 1] |= (1 << this.DB - o) - 1 << o)), this.clamp(), s && t.ZERO.subTo(this, this)
+ }, t.prototype.clamp = function () {
+ for (var t = this.s & this.DM; this.t > 0 && this[this.t - 1] == t;) --this.t
+ }, t.prototype.dlShiftTo = function (t, e) {
+ var i;
+ for (i = this.t - 1; i >= 0; --i) e[i + t] = this[i];
+ for (i = t - 1; i >= 0; --i) e[i] = 0;
+ e.t = this.t + t, e.s = this.s
+ }, t.prototype.drShiftTo = function (t, e) {
+ for (var i = t; i < this.t; ++i) e[i - t] = this[i];
+ e.t = Math.max(this.t - t, 0), e.s = this.s
+ }, t.prototype.lShiftTo = function (t, e) {
+ for (var i = t % this.DB, r = this.DB - i, n = (1 << r) - 1, s = Math.floor(t / this.DB), o = this.s << i & this.DM, h = this.t - 1; h >= 0; --h) e[h + s + 1] = this[h] >> r | o, o = (this[h] & n) << i;
+ for (h = s - 1; h >= 0; --h) e[h] = 0;
+ e[s] = o, e.t = this.t + s + 1, e.s = this.s, e.clamp()
+ }, t.prototype.rShiftTo = function (t, e) {
+ e.s = this.s;
+ var i = Math.floor(t / this.DB);
+ if (i >= this.t) e.t = 0; else {
+ var r = t % this.DB, n = this.DB - r, s = (1 << r) - 1;
+ e[0] = this[i] >> r;
+ for (var o = i + 1; o < this.t; ++o) e[o - i - 1] |= (this[o] & s) << n, e[o - i] = this[o] >> r;
+ r > 0 && (e[this.t - i - 1] |= (this.s & s) << n), e.t = this.t - i, e.clamp()
+ }
+ }, t.prototype.subTo = function (t, e) {
+ for (var i = 0, r = 0, n = Math.min(t.t, this.t); i < n;) r += this[i] - t[i], e[i++] = r & this.DM, r >>= this.DB;
+ if (t.t < this.t) {
+ for (r -= t.s; i < this.t;) r += this[i], e[i++] = r & this.DM, r >>= this.DB;
+ r += this.s
+ } else {
+ for (r += this.s; i < t.t;) r -= t[i], e[i++] = r & this.DM, r >>= this.DB;
+ r -= t.s
+ }
+ e.s = r < 0 ? -1 : 0, r < -1 ? e[i++] = this.DV + r : r > 0 && (e[i++] = r), e.t = i, e.clamp()
+ }, t.prototype.multiplyTo = function (e, i) {
+ var r = this.abs(), n = e.abs(), s = r.t;
+ for (i.t = s + n.t; --s >= 0;) i[s] = 0;
+ for (s = 0; s < n.t; ++s) i[s + r.t] = r.am(0, n[s], i, s, 0, r.t);
+ i.s = 0, i.clamp(), this.s != e.s && t.ZERO.subTo(i, i)
+ }, t.prototype.squareTo = function (t) {
+ for (var e = this.abs(), i = t.t = 2 * e.t; --i >= 0;) t[i] = 0;
+ for (i = 0; i < e.t - 1; ++i) {
+ var r = e.am(i, e[i], t, 2 * i, 0, 1);
+ (t[i + e.t] += e.am(i + 1, 2 * e[i], t, 2 * i + 1, r, e.t - i - 1)) >= e.DV && (t[i + e.t] -= e.DV, t[i + e.t + 1] = 1)
+ }
+ t.t > 0 && (t[t.t - 1] += e.am(i, e[i], t, 2 * i, 0, 1)), t.s = 0, t.clamp()
+ }, t.prototype.divRemTo = function (e, i, r) {
+ var n = e.abs();
+ if (!(n.t <= 0)) {
+ var s = this.abs();
+ if (s.t < n.t) return null != i && i.fromInt(0), void (null != r && this.copyTo(r));
+ null == r && (r = N());
+ var o = N(), h = this.s, a = e.s, u = this.DB - F(n[n.t - 1]);
+ u > 0 ? (n.lShiftTo(u, o), s.lShiftTo(u, r)) : (n.copyTo(o), s.copyTo(r));
+ var c = o.t, f = o[c - 1];
+ if (0 != f) {
+ var l = f * (1 << this.F1) + (c > 1 ? o[c - 2] >> this.F2 : 0), p = this.FV / l,
+ g = (1 << this.F1) / l, d = 1 << this.F2, v = r.t, m = v - c,
+ y = null == i ? N() : i;
+ for (o.dlShiftTo(m, y), r.compareTo(y) >= 0 && (r[r.t++] = 1, r.subTo(y, r)), t.ONE.dlShiftTo(c, y), y.subTo(o, o); o.t < c;) o[o.t++] = 0;
+ for (; --m >= 0;) {
+ var b = r[--v] == f ? this.DM : Math.floor(r[v] * p + (r[v - 1] + d) * g);
+ if ((r[v] += o.am(0, b, r, m, 0, c)) < b) for (o.dlShiftTo(m, y), r.subTo(y, r); r[v] < --b;) r.subTo(y, r)
+ }
+ null != i && (r.drShiftTo(c, i), h != a && t.ZERO.subTo(i, i)), r.t = c, r.clamp(), u > 0 && r.rShiftTo(u, r), h < 0 && t.ZERO.subTo(r, r)
+ }
+ }
+ }, t.prototype.invDigit = function () {
+ if (this.t < 1) return 0;
+ var t = this[0];
+ if (0 == (1 & t)) return 0;
+ var e = 3 & t;
+ return (e = (e = (e = (e = e * (2 - (15 & t) * e) & 15) * (2 - (255 & t) * e) & 255) * (2 - ((65535 & t) * e & 65535)) & 65535) * (2 - t * e % this.DV) % this.DV) > 0 ? this.DV - e : -e
+ }, t.prototype.isEven = function () {
+ return 0 == (this.t > 0 ? 1 & this[0] : this.s)
+ }, t.prototype.exp = function (e, i) {
+ if (e > 4294967295 || e < 1) return t.ONE;
+ var r = N(), n = N(), s = i.convert(this), o = F(e) - 1;
+ for (s.copyTo(r); --o >= 0;) if (i.sqrTo(r, n), (e & 1 << o) > 0) i.mulTo(n, s, r); else {
+ var h = r;
+ r = n, n = h
+ }
+ return i.revert(r)
+ }, t.prototype.chunkSize = function (t) {
+ return Math.floor(Math.LN2 * this.DB / Math.log(t))
+ }, t.prototype.toRadix = function (t) {
+ if (null == t && (t = 10), 0 == this.signum() || t < 2 || t > 36) return "0";
+ var e = this.chunkSize(t), i = Math.pow(t, e), r = C(i), n = N(), s = N(), o = "";
+ for (this.divRemTo(r, n, s); n.signum() > 0;) o = (i + s.intValue()).toString(t).substr(1) + o, n.divRemTo(r, n, s);
+ return s.intValue().toString(t) + o
+ }, t.prototype.fromRadix = function (e, i) {
+ this.fromInt(0), null == i && (i = 10);
+ for (var r = this.chunkSize(i), n = Math.pow(i, r), s = !1, o = 0, h = 0, a = 0; a < e.length; ++a) {
+ var u = H(e, a);
+ u < 0 ? "-" == e.charAt(a) && 0 == this.signum() && (s = !0) : (h = i * h + u, ++o >= r && (this.dMultiply(n), this.dAddOffset(h, 0), o = 0, h = 0))
+ }
+ o > 0 && (this.dMultiply(Math.pow(i, o)), this.dAddOffset(h, 0)), s && t.ZERO.subTo(this, this)
+ }, t.prototype.fromNumber = function (e, i, r) {
+ if ("number" == typeof i) if (e < 2) this.fromInt(1); else for (this.fromNumber(e, r), this.testBit(e - 1) || this.bitwiseTo(t.ONE.shiftLeft(e - 1), s, this), this.isEven() && this.dAddOffset(1, 0); !this.isProbablePrime(i);) this.dAddOffset(2, 0), this.bitLength() > e && this.subTo(t.ONE.shiftLeft(e - 1), this); else {
+ var n = [], o = 7 & e;
+ n.length = 1 + (e >> 3), i.nextBytes(n), o > 0 ? n[0] &= (1 << o) - 1 : n[0] = 0, this.fromString(n, 256)
+ }
+ }, t.prototype.bitwiseTo = function (t, e, i) {
+ var r, n, s = Math.min(t.t, this.t);
+ for (r = 0; r < s; ++r) i[r] = e(this[r], t[r]);
+ if (t.t < this.t) {
+ for (n = t.s & this.DM, r = s; r < this.t; ++r) i[r] = e(this[r], n);
+ i.t = this.t
+ } else {
+ for (n = this.s & this.DM, r = s; r < t.t; ++r) i[r] = e(n, t[r]);
+ i.t = t.t
+ }
+ i.s = e(this.s, t.s), i.clamp()
+ }, t.prototype.changeBit = function (e, i) {
+ var r = t.ONE.shiftLeft(e);
+ return this.bitwiseTo(r, i, r), r
+ }, t.prototype.addTo = function (t, e) {
+ for (var i = 0, r = 0, n = Math.min(t.t, this.t); i < n;) r += this[i] + t[i], e[i++] = r & this.DM, r >>= this.DB;
+ if (t.t < this.t) {
+ for (r += t.s; i < this.t;) r += this[i], e[i++] = r & this.DM, r >>= this.DB;
+ r += this.s
+ } else {
+ for (r += this.s; i < t.t;) r += t[i], e[i++] = r & this.DM, r >>= this.DB;
+ r += t.s
+ }
+ e.s = r < 0 ? -1 : 0, r > 0 ? e[i++] = r : r < -1 && (e[i++] = this.DV + r), e.t = i, e.clamp()
+ }, t.prototype.dMultiply = function (t) {
+ this[this.t] = this.am(0, t - 1, this, 0, 0, this.t), ++this.t, this.clamp()
+ }, t.prototype.dAddOffset = function (t, e) {
+ if (0 != t) {
+ for (; this.t <= e;) this[this.t++] = 0;
+ for (this[e] += t; this[e] >= this.DV;) this[e] -= this.DV, ++e >= this.t && (this[this.t++] = 0), ++this[e]
+ }
+ }, t.prototype.multiplyLowerTo = function (t, e, i) {
+ var r = Math.min(this.t + t.t, e);
+ for (i.s = 0, i.t = r; r > 0;) i[--r] = 0;
+ for (var n = i.t - this.t; r < n; ++r) i[r + this.t] = this.am(0, t[r], i, r, 0, this.t);
+ for (n = Math.min(t.t, e); r < n; ++r) this.am(0, t[r], i, r, 0, e - r);
+ i.clamp()
+ }, t.prototype.multiplyUpperTo = function (t, e, i) {
+ --e;
+ var r = i.t = this.t + t.t - e;
+ for (i.s = 0; --r >= 0;) i[r] = 0;
+ for (r = Math.max(e - this.t, 0); r < t.t; ++r) i[this.t + r - e] = this.am(e - r, t[r], i, 0, 0, this.t + r - e);
+ i.clamp(), i.drShiftTo(1, i)
+ }, t.prototype.modInt = function (t) {
+ if (t <= 0) return 0;
+ var e = this.DV % t, i = this.s < 0 ? t - 1 : 0;
+ if (this.t > 0) if (0 == e) i = this[0] % t; else for (var r = this.t - 1; r >= 0; --r) i = (e * i + this[r]) % t;
+ return i
+ }, t.prototype.millerRabin = function (e) {
+ var i = this.subtract(t.ONE), r = i.getLowestSetBit();
+ if (r <= 0) return !1;
+ var n = i.shiftRight(r);
+ (e = e + 1 >> 1) > x.length && (e = x.length);
+ for (var s = N(), o = 0; o < e; ++o) {
+ s.fromInt(x[Math.floor(Math.random() * x.length)]);
+ var h = s.modPow(n, this);
+ if (0 != h.compareTo(t.ONE) && 0 != h.compareTo(i)) {
+ for (var a = 1; a++ < r && 0 != h.compareTo(i);) if (0 == (h = h.modPowInt(2, this)).compareTo(t.ONE)) return !1;
+ if (0 != h.compareTo(i)) return !1
+ }
+ }
+ return !0
+ }, t.prototype.square = function () {
+ var t = N();
+ return this.squareTo(t), t
+ }, t.prototype.gcda = function (t, e) {
+ var i = this.s < 0 ? this.negate() : this.clone(), r = t.s < 0 ? t.negate() : t.clone();
+ if (i.compareTo(r) < 0) {
+ var n = i;
+ i = r, r = n
+ }
+ var s = i.getLowestSetBit(), o = r.getLowestSetBit();
+ if (o < 0) e(i); else {
+ s < o && (o = s), o > 0 && (i.rShiftTo(o, i), r.rShiftTo(o, r));
+ var h = function () {
+ (s = i.getLowestSetBit()) > 0 && i.rShiftTo(s, i), (s = r.getLowestSetBit()) > 0 && r.rShiftTo(s, r), i.compareTo(r) >= 0 ? (i.subTo(r, i), i.rShiftTo(1, i)) : (r.subTo(i, r), r.rShiftTo(1, r)), i.signum() > 0 ? setTimeout(h, 0) : (o > 0 && r.lShiftTo(o, r), setTimeout((function () {
+ e(r)
+ }), 0))
+ };
+ setTimeout(h, 10)
+ }
+ }, t.prototype.fromNumberAsync = function (e, i, r, n) {
+ if ("number" == typeof i) if (e < 2) this.fromInt(1); else {
+ this.fromNumber(e, r), this.testBit(e - 1) || this.bitwiseTo(t.ONE.shiftLeft(e - 1), s, this), this.isEven() && this.dAddOffset(1, 0);
+ var o = this, h = function () {
+ o.dAddOffset(2, 0), o.bitLength() > e && o.subTo(t.ONE.shiftLeft(e - 1), o), o.isProbablePrime(i) ? setTimeout((function () {
+ n()
+ }), 0) : setTimeout(h, 0)
+ };
+ setTimeout(h, 0)
+ } else {
+ var a = [], u = 7 & e;
+ a.length = 1 + (e >> 3), i.nextBytes(a), u > 0 ? a[0] &= (1 << u) - 1 : a[0] = 0, this.fromString(a, 256)
+ }
+ }, t
+ }(), O = function () {
+ function t() {
+ }
+
+ return t.prototype.convert = function (t) {
+ return t
+ }, t.prototype.revert = function (t) {
+ return t
+ }, t.prototype.mulTo = function (t, e, i) {
+ t.multiplyTo(e, i)
+ }, t.prototype.sqrTo = function (t, e) {
+ t.squareTo(e)
+ }, t
+ }(), A = function () {
+ function t(t) {
+ this.m = t
+ }
+
+ return t.prototype.convert = function (t) {
+ return t.s < 0 || t.compareTo(this.m) >= 0 ? t.mod(this.m) : t
+ }, t.prototype.revert = function (t) {
+ return t
+ }, t.prototype.reduce = function (t) {
+ t.divRemTo(this.m, null, t)
+ }, t.prototype.mulTo = function (t, e, i) {
+ t.multiplyTo(e, i), this.reduce(i)
+ }, t.prototype.sqrTo = function (t, e) {
+ t.squareTo(e), this.reduce(e)
+ }, t
+ }(), V = function () {
+ function t(t) {
+ this.m = t, this.mp = t.invDigit(), this.mpl = 32767 & this.mp, this.mph = this.mp >> 15, this.um = (1 << t.DB - 15) - 1, this.mt2 = 2 * t.t
+ }
+
+ return t.prototype.convert = function (t) {
+ var e = N();
+ return t.abs().dlShiftTo(this.m.t, e), e.divRemTo(this.m, null, e), t.s < 0 && e.compareTo(B.ZERO) > 0 && this.m.subTo(e, e), e
+ }, t.prototype.revert = function (t) {
+ var e = N();
+ return t.copyTo(e), this.reduce(e), e
+ }, t.prototype.reduce = function (t) {
+ for (; t.t <= this.mt2;) t[t.t++] = 0;
+ for (var e = 0; e < this.m.t; ++e) {
+ var i = 32767 & t[e],
+ r = i * this.mpl + ((i * this.mph + (t[e] >> 15) * this.mpl & this.um) << 15) & t.DM;
+ for (t[i = e + this.m.t] += this.m.am(0, r, t, e, 0, this.m.t); t[i] >= t.DV;) t[i] -= t.DV, t[++i]++
+ }
+ t.clamp(), t.drShiftTo(this.m.t, t), t.compareTo(this.m) >= 0 && t.subTo(this.m, t)
+ }, t.prototype.mulTo = function (t, e, i) {
+ t.multiplyTo(e, i), this.reduce(i)
+ }, t.prototype.sqrTo = function (t, e) {
+ t.squareTo(e), this.reduce(e)
+ }, t
+ }(), I = function () {
+ function t(t) {
+ this.m = t, this.r2 = N(), this.q3 = N(), B.ONE.dlShiftTo(2 * t.t, this.r2), this.mu = this.r2.divide(t)
+ }
+
+ return t.prototype.convert = function (t) {
+ if (t.s < 0 || t.t > 2 * this.m.t) return t.mod(this.m);
+ if (t.compareTo(this.m) < 0) return t;
+ var e = N();
+ return t.copyTo(e), this.reduce(e), e
+ }, t.prototype.revert = function (t) {
+ return t
+ }, t.prototype.reduce = function (t) {
+ for (t.drShiftTo(this.m.t - 1, this.r2), t.t > this.m.t + 1 && (t.t = this.m.t + 1, t.clamp()), this.mu.multiplyUpperTo(this.r2, this.m.t + 1, this.q3), this.m.multiplyLowerTo(this.q3, this.m.t + 1, this.r2); t.compareTo(this.r2) < 0;) t.dAddOffset(1, this.m.t + 1);
+ for (t.subTo(this.r2, t); t.compareTo(this.m) >= 0;) t.subTo(this.m, t)
+ }, t.prototype.mulTo = function (t, e, i) {
+ t.multiplyTo(e, i), this.reduce(i)
+ }, t.prototype.sqrTo = function (t, e) {
+ t.squareTo(e), this.reduce(e)
+ }, t
+ }();
+
+ function N() {
+ return new B(null)
+ }
+
+ function P(t, e) {
+ return new B(t, e)
+ }
+
+ var M = "undefined" != typeof navigator;
+ M && "Microsoft Internet Explorer" == navigator.appName ? (B.prototype.am = function (t, e, i, r, n, s) {
+ for (var o = 32767 & e, h = e >> 15; --s >= 0;) {
+ var a = 32767 & this[t], u = this[t++] >> 15, c = h * a + u * o;
+ n = ((a = o * a + ((32767 & c) << 15) + i[r] + (1073741823 & n)) >>> 30) + (c >>> 15) + h * u + (n >>> 30), i[r++] = 1073741823 & a
+ }
+ return n
+ }, S = 30) : M && "Netscape" != navigator.appName ? (B.prototype.am = function (t, e, i, r, n, s) {
+ for (; --s >= 0;) {
+ var o = e * this[t++] + i[r] + n;
+ n = Math.floor(o / 67108864), i[r++] = 67108863 & o
+ }
+ return n
+ }, S = 26) : (B.prototype.am = function (t, e, i, r, n, s) {
+ for (var o = 16383 & e, h = e >> 14; --s >= 0;) {
+ var a = 16383 & this[t], u = this[t++] >> 14, c = h * a + u * o;
+ n = ((a = o * a + ((16383 & c) << 14) + i[r] + n) >> 28) + (c >> 14) + h * u, i[r++] = 268435455 & a
+ }
+ return n
+ }, S = 28), B.prototype.DB = S, B.prototype.DM = (1 << S) - 1, B.prototype.DV = 1 << S, B.prototype.FV = Math.pow(2, 52), B.prototype.F1 = 52 - S, B.prototype.F2 = 2 * S - 52;
+ var j, q, L = [];
+ for (j = "0".charCodeAt(0), q = 0; q <= 9; ++q) L[j++] = q;
+ for (j = "a".charCodeAt(0), q = 10; q < 36; ++q) L[j++] = q;
+ for (j = "A".charCodeAt(0), q = 10; q < 36; ++q) L[j++] = q;
+
+ function H(t, e) {
+ var i = L[t.charCodeAt(e)];
+ return null == i ? -1 : i
+ }
+
+ function C(t) {
+ var e = N();
+ return e.fromInt(t), e
+ }
+
+ function F(t) {
+ var e, i = 1;
+ return 0 != (e = t >>> 16) && (t = e, i += 16), 0 != (e = t >> 8) && (t = e, i += 8), 0 != (e = t >> 4) && (t = e, i += 4), 0 != (e = t >> 2) && (t = e, i += 2), 0 != (e = t >> 1) && (t = e, i += 1), i
+ }
+
+ B.ZERO = C(0), B.ONE = C(1);
+ var U, K, k = function () {
+ function t() {
+ this.i = 0, this.j = 0, this.S = []
+ }
+
+ return t.prototype.init = function (t) {
+ var e, i, r;
+ for (e = 0; e < 256; ++e) this.S[e] = e;
+ for (i = 0, e = 0; e < 256; ++e) i = i + this.S[e] + t[e % t.length] & 255, r = this.S[e], this.S[e] = this.S[i], this.S[i] = r;
+ this.i = 0, this.j = 0
+ }, t.prototype.next = function () {
+ var t;
+ return this.i = this.i + 1 & 255, this.j = this.j + this.S[this.i] & 255, t = this.S[this.i], this.S[this.i] = this.S[this.j], this.S[this.j] = t, this.S[t + this.S[this.i] & 255]
+ }, t
+ }(), _ = null;
+ if (null == _) {
+ _ = [], K = 0;
+ var z = void 0;
+ if (window.crypto && window.crypto.getRandomValues) {
+ var Z = new Uint32Array(256);
+ for (window.crypto.getRandomValues(Z), z = 0; z < Z.length; ++z) _[K++] = 255 & Z[z]
+ }
+ var G = 0, $ = function (t) {
+ if ((G = G || 0) >= 256 || K >= 256) window.removeEventListener ? window.removeEventListener("mousemove", $, !1) : window.detachEvent && window.detachEvent("onmousemove", $); else try {
+ var e = t.x + t.y;
+ _[K++] = 255 & e, G += 1
+ } catch (t) {
+ }
+ };
+ window.addEventListener ? window.addEventListener("mousemove", $, !1) : window.attachEvent && window.attachEvent("onmousemove", $)
+ }
+
+ function Y() {
+ if (null == U) {
+ for (U = new k; K < 256;) {
+ var t = Math.floor(65536 * Math.random());
+ _[K++] = 255 & t
+ }
+ for (U.init(_), K = 0; K < _.length; ++K) _[K] = 0;
+ K = 0
+ }
+ return U.next()
+ }
+
+ var J = function () {
+ function t() {
+ }
+
+ return t.prototype.nextBytes = function (t) {
+ for (var e = 0; e < t.length; ++e) t[e] = Y()
+ }, t
+ }(), X = function () {
+ function t() {
+ this.n = null, this.e = 0, this.d = null, this.p = null, this.q = null, this.dmp1 = null, this.dmq1 = null, this.coeff = null
+ }
+
+ return t.prototype.doPublic = function (t) {
+ return t.modPowInt(this.e, this.n)
+ }, t.prototype.doPrivate = function (t) {
+ if (null == this.p || null == this.q) return t.modPow(this.d, this.n);
+ for (var e = t.mod(this.p).modPow(this.dmp1, this.p), i = t.mod(this.q).modPow(this.dmq1, this.q); e.compareTo(i) < 0;) e = e.add(this.p);
+ return e.subtract(i).multiply(this.coeff).mod(this.p).multiply(this.q).add(i)
+ }, t.prototype.setPublic = function (t, e) {
+ null != t && null != e && t.length > 0 && e.length > 0 ? (this.n = P(t, 16), this.e = parseInt(e, 16)) : console.error("Invalid RSA public key")
+ }, t.prototype.encrypt = function (t) {
+ var e = function (t, e) {
+ if (e < t.length + 11) return console.error("Message too long for RSA"), null;
+ for (var i = [], r = t.length - 1; r >= 0 && e > 0;) {
+ var n = t.charCodeAt(r--);
+ n < 128 ? i[--e] = n : n > 127 && n < 2048 ? (i[--e] = 63 & n | 128, i[--e] = n >> 6 | 192) : (i[--e] = 63 & n | 128, i[--e] = n >> 6 & 63 | 128, i[--e] = n >> 12 | 224)
+ }
+ i[--e] = 0;
+ for (var s = new J, o = []; e > 2;) {
+ for (o[0] = 0; 0 == o[0];) s.nextBytes(o);
+ i[--e] = o[0]
+ }
+ return i[--e] = 2, i[--e] = 0, new B(i)
+ }(t, this.n.bitLength() + 7 >> 3);
+ if (null == e) return null;
+ var i = this.doPublic(e);
+ if (null == i) return null;
+ var r = i.toString(16);
+ return 0 == (1 & r.length) ? r : "0" + r
+ }, t.prototype.setPrivate = function (t, e, i) {
+ null != t && null != e && t.length > 0 && e.length > 0 ? (this.n = P(t, 16), this.e = parseInt(e, 16), this.d = P(i, 16)) : console.error("Invalid RSA private key")
+ }, t.prototype.setPrivateEx = function (t, e, i, r, n, s, o, h) {
+ null != t && null != e && t.length > 0 && e.length > 0 ? (this.n = P(t, 16), this.e = parseInt(e, 16), this.d = P(i, 16), this.p = P(r, 16), this.q = P(n, 16), this.dmp1 = P(s, 16), this.dmq1 = P(o, 16), this.coeff = P(h, 16)) : console.error("Invalid RSA private key")
+ }, t.prototype.generate = function (t, e) {
+ var i = new J, r = t >> 1;
+ this.e = parseInt(e, 16);
+ for (var n = new B(e, 16); ;) {
+ for (; this.p = new B(t - r, 1, i), 0 != this.p.subtract(B.ONE).gcd(n).compareTo(B.ONE) || !this.p.isProbablePrime(10);) ;
+ for (; this.q = new B(r, 1, i), 0 != this.q.subtract(B.ONE).gcd(n).compareTo(B.ONE) || !this.q.isProbablePrime(10);) ;
+ if (this.p.compareTo(this.q) <= 0) {
+ var s = this.p;
+ this.p = this.q, this.q = s
+ }
+ var o = this.p.subtract(B.ONE), h = this.q.subtract(B.ONE), a = o.multiply(h);
+ if (0 == a.gcd(n).compareTo(B.ONE)) {
+ this.n = this.p.multiply(this.q), this.d = n.modInverse(a), this.dmp1 = this.d.mod(o), this.dmq1 = this.d.mod(h), this.coeff = this.q.modInverse(this.p);
+ break
+ }
+ }
+ }, t.prototype.decrypt = function (t) {
+ var e = P(t, 16), i = this.doPrivate(e);
+ return null == i ? null : function (t, e) {
+ for (var i = t.toByteArray(), r = 0; r < i.length && 0 == i[r];) ++r;
+ if (i.length - r != e - 1 || 2 != i[r]) return null;
+ for (++r; 0 != i[r];) if (++r >= i.length) return null;
+ for (var n = ""; ++r < i.length;) {
+ var s = 255 & i[r];
+ s < 128 ? n += String.fromCharCode(s) : s > 191 && s < 224 ? (n += String.fromCharCode((31 & s) << 6 | 63 & i[r + 1]), ++r) : (n += String.fromCharCode((15 & s) << 12 | (63 & i[r + 1]) << 6 | 63 & i[r + 2]), r += 2)
+ }
+ return n
+ }(i, this.n.bitLength() + 7 >> 3)
+ }, t.prototype.generateAsync = function (t, e, i) {
+ var r = new J, n = t >> 1;
+ this.e = parseInt(e, 16);
+ var s = new B(e, 16), o = this, h = function () {
+ var e = function () {
+ if (o.p.compareTo(o.q) <= 0) {
+ var t = o.p;
+ o.p = o.q, o.q = t
+ }
+ var e = o.p.subtract(B.ONE), r = o.q.subtract(B.ONE), n = e.multiply(r);
+ 0 == n.gcd(s).compareTo(B.ONE) ? (o.n = o.p.multiply(o.q), o.d = s.modInverse(n), o.dmp1 = o.d.mod(e), o.dmq1 = o.d.mod(r), o.coeff = o.q.modInverse(o.p), setTimeout((function () {
+ i()
+ }), 0)) : setTimeout(h, 0)
+ }, a = function () {
+ o.q = N(), o.q.fromNumberAsync(n, 1, r, (function () {
+ o.q.subtract(B.ONE).gcda(s, (function (t) {
+ 0 == t.compareTo(B.ONE) && o.q.isProbablePrime(10) ? setTimeout(e, 0) : setTimeout(a, 0)
+ }))
+ }))
+ }, u = function () {
+ o.p = N(), o.p.fromNumberAsync(t - n, 1, r, (function () {
+ o.p.subtract(B.ONE).gcda(s, (function (t) {
+ 0 == t.compareTo(B.ONE) && o.p.isProbablePrime(10) ? setTimeout(a, 0) : setTimeout(u, 0)
+ }))
+ }))
+ };
+ setTimeout(u, 0)
+ };
+ setTimeout(h, 0)
+ }, t.prototype.sign = function (t, e, i) {
+ var r = function (t, e) {
+ if (e < t.length + 22) return console.error("Message too long for RSA"), null;
+ for (var i = e - t.length - 6, r = "", n = 0; n < i; n += 2) r += "ff";
+ return P("0001" + r + "00" + t, 16)
+ }((Q[i] || "") + e(t).toString(), this.n.bitLength() / 4);
+ if (null == r) return null;
+ var n = this.doPrivate(r);
+ if (null == n) return null;
+ var s = n.toString(16);
+ return 0 == (1 & s.length) ? s : "0" + s
+ }, t.prototype.verify = function (t, e, i) {
+ var r = P(e, 16), n = this.doPublic(r);
+ return null == n ? null : function (t) {
+ for (var e in Q) if (Q.hasOwnProperty(e)) {
+ var i = Q[e], r = i.length;
+ if (t.substr(0, r) == i) return t.substr(r)
+ }
+ return t
+ }(n.toString(16).replace(/^1f+00/, "")) == i(t).toString()
+ }, t
+ }(), Q = {
+ md2: "3020300c06082a864886f70d020205000410",
+ md5: "3020300c06082a864886f70d020505000410",
+ sha1: "3021300906052b0e03021a05000414",
+ sha224: "302d300d06096086480165030402040500041c",
+ sha256: "3031300d060960864801650304020105000420",
+ sha384: "3041300d060960864801650304020205000430",
+ sha512: "3051300d060960864801650304020305000440",
+ ripemd160: "3021300906052b2403020105000414"
+ }, W = {};
+ W.lang = {
+ extend: function (t, e, i) {
+ if (!e || !t) throw new Error("YAHOO.lang.extend failed, please check that all dependencies are included.");
+ var r = function () {
+ };
+ if (r.prototype = e.prototype, t.prototype = new r, t.prototype.constructor = t, t.superclass = e.prototype, e.prototype.constructor == Object.prototype.constructor && (e.prototype.constructor = e), i) {
+ var n;
+ for (n in i) t.prototype[n] = i[n];
+ var s = function () {
+ }, o = ["toString", "valueOf"];
+ try {
+ /MSIE/.test(navigator.userAgent) && (s = function (t, e) {
+ for (n = 0; n < o.length; n += 1) {
+ var i = o[n], r = e[i];
+ "function" == typeof r && r != Object.prototype[i] && (t[i] = r)
+ }
+ })
+ } catch (t) {
+ }
+ s(t.prototype, i)
+ }
+ }
+ };
+ var tt = {};
+ void 0 !== tt.asn1 && tt.asn1 || (tt.asn1 = {}), tt.asn1.ASN1Util = new function () {
+ this.integerToByteHex = function (t) {
+ var e = t.toString(16);
+ return e.length % 2 == 1 && (e = "0" + e), e
+ }, this.bigIntToMinTwosComplementsHex = function (t) {
+ var e = t.toString(16);
+ if ("-" != e.substr(0, 1)) e.length % 2 == 1 ? e = "0" + e : e.match(/^[0-7]/) || (e = "00" + e); else {
+ var i = e.substr(1).length;
+ i % 2 == 1 ? i += 1 : e.match(/^[0-7]/) || (i += 2);
+ for (var r = "", n = 0; n < i; n++) r += "f";
+ e = new B(r, 16).xor(t).add(B.ONE).toString(16).replace(/^-/, "")
+ }
+ return e
+ }, this.getPEMStringFromHex = function (t, e) {
+ return hextopem(t, e)
+ }, this.newObject = function (t) {
+ var e = tt.asn1, i = e.DERBoolean, r = e.DERInteger, n = e.DERBitString, s = e.DEROctetString,
+ o = e.DERNull, h = e.DERObjectIdentifier, a = e.DEREnumerated, u = e.DERUTF8String,
+ c = e.DERNumericString, f = e.DERPrintableString, l = e.DERTeletexString,
+ p = e.DERIA5String, g = e.DERUTCTime, d = e.DERGeneralizedTime, v = e.DERSequence,
+ m = e.DERSet, y = e.DERTaggedObject, b = e.ASN1Util.newObject, T = Object.keys(t);
+ if (1 != T.length) throw"key of param shall be only one.";
+ var S = T[0];
+ if (-1 == ":bool:int:bitstr:octstr:null:oid:enum:utf8str:numstr:prnstr:telstr:ia5str:utctime:gentime:seq:set:tag:".indexOf(":" + S + ":")) throw"undefined key: " + S;
+ if ("bool" == S) return new i(t[S]);
+ if ("int" == S) return new r(t[S]);
+ if ("bitstr" == S) return new n(t[S]);
+ if ("octstr" == S) return new s(t[S]);
+ if ("null" == S) return new o(t[S]);
+ if ("oid" == S) return new h(t[S]);
+ if ("enum" == S) return new a(t[S]);
+ if ("utf8str" == S) return new u(t[S]);
+ if ("numstr" == S) return new c(t[S]);
+ if ("prnstr" == S) return new f(t[S]);
+ if ("telstr" == S) return new l(t[S]);
+ if ("ia5str" == S) return new p(t[S]);
+ if ("utctime" == S) return new g(t[S]);
+ if ("gentime" == S) return new d(t[S]);
+ if ("seq" == S) {
+ for (var E = t[S], w = [], D = 0; D < E.length; D++) {
+ var x = b(E[D]);
+ w.push(x)
+ }
+ return new v({array: w})
+ }
+ if ("set" == S) {
+ for (E = t[S], w = [], D = 0; D < E.length; D++) x = b(E[D]), w.push(x);
+ return new m({array: w})
+ }
+ if ("tag" == S) {
+ var R = t[S];
+ if ("[object Array]" === Object.prototype.toString.call(R) && 3 == R.length) {
+ var B = b(R[2]);
+ return new y({tag: R[0], explicit: R[1], obj: B})
+ }
+ var O = {};
+ if (void 0 !== R.explicit && (O.explicit = R.explicit), void 0 !== R.tag && (O.tag = R.tag), void 0 === R.obj) throw"obj shall be specified for 'tag'.";
+ return O.obj = b(R.obj), new y(O)
+ }
+ }, this.jsonToASN1HEX = function (t) {
+ return this.newObject(t).getEncodedHex()
+ }
+ }, tt.asn1.ASN1Util.oidHexToInt = function (t) {
+ for (var e = "", i = parseInt(t.substr(0, 2), 16), r = (e = Math.floor(i / 40) + "." + i % 40, ""), n = 2; n < t.length; n += 2) {
+ var s = ("00000000" + parseInt(t.substr(n, 2), 16).toString(2)).slice(-8);
+ r += s.substr(1, 7), "0" == s.substr(0, 1) && (e = e + "." + new B(r, 2).toString(10), r = "")
+ }
+ return e
+ }, tt.asn1.ASN1Util.oidIntToHex = function (t) {
+ var e = function (t) {
+ var e = t.toString(16);
+ return 1 == e.length && (e = "0" + e), e
+ }, i = function (t) {
+ var i = "", r = new B(t, 10).toString(2), n = 7 - r.length % 7;
+ 7 == n && (n = 0);
+ for (var s = "", o = 0; o < n; o++) s += "0";
+ for (r = s + r, o = 0; o < r.length - 1; o += 7) {
+ var h = r.substr(o, 7);
+ o != r.length - 7 && (h = "1" + h), i += e(parseInt(h, 2))
+ }
+ return i
+ };
+ if (!t.match(/^[0-9.]+$/)) throw"malformed oid string: " + t;
+ var r = "", n = t.split("."), s = 40 * parseInt(n[0]) + parseInt(n[1]);
+ r += e(s), n.splice(0, 2);
+ for (var o = 0; o < n.length; o++) r += i(n[o]);
+ return r
+ }, tt.asn1.ASN1Object = function () {
+ this.getLengthHexFromValue = function () {
+ if (void 0 === this.hV || null == this.hV) throw"this.hV is null or undefined.";
+ if (this.hV.length % 2 == 1) throw"value hex must be even length: n=" + "".length + ",v=" + this.hV;
+ var t = this.hV.length / 2, e = t.toString(16);
+ if (e.length % 2 == 1 && (e = "0" + e), t < 128) return e;
+ var i = e.length / 2;
+ if (i > 15) throw"ASN.1 length too long to represent by 8x: n = " + t.toString(16);
+ return (128 + i).toString(16) + e
+ }, this.getEncodedHex = function () {
+ return (null == this.hTLV || this.isModified) && (this.hV = this.getFreshValueHex(), this.hL = this.getLengthHexFromValue(), this.hTLV = this.hT + this.hL + this.hV, this.isModified = !1), this.hTLV
+ }, this.getValueHex = function () {
+ return this.getEncodedHex(), this.hV
+ }, this.getFreshValueHex = function () {
+ return ""
+ }
+ }, tt.asn1.DERAbstractString = function (t) {
+ tt.asn1.DERAbstractString.superclass.constructor.call(this), this.getString = function () {
+ return this.s
+ }, this.setString = function (t) {
+ this.hTLV = null, this.isModified = !0, this.s = t, this.hV = stohex(this.s)
+ }, this.setStringHex = function (t) {
+ this.hTLV = null, this.isModified = !0, this.s = null, this.hV = t
+ }, this.getFreshValueHex = function () {
+ return this.hV
+ }, void 0 !== t && ("string" == typeof t ? this.setString(t) : void 0 !== t.str ? this.setString(t.str) : void 0 !== t.hex && this.setStringHex(t.hex))
+ }, W.lang.extend(tt.asn1.DERAbstractString, tt.asn1.ASN1Object), tt.asn1.DERAbstractTime = function (t) {
+ tt.asn1.DERAbstractTime.superclass.constructor.call(this), this.localDateToUTC = function (t) {
+ return utc = t.getTime() + 6e4 * t.getTimezoneOffset(), new Date(utc)
+ }, this.formatDate = function (t, e, i) {
+ var r = this.zeroPadding, n = this.localDateToUTC(t), s = String(n.getFullYear());
+ "utc" == e && (s = s.substr(2, 2));
+ var o = s + r(String(n.getMonth() + 1), 2) + r(String(n.getDate()), 2) + r(String(n.getHours()), 2) + r(String(n.getMinutes()), 2) + r(String(n.getSeconds()), 2);
+ if (!0 === i) {
+ var h = n.getMilliseconds();
+ if (0 != h) {
+ var a = r(String(h), 3);
+ o = o + "." + (a = a.replace(/[0]+$/, ""))
+ }
+ }
+ return o + "Z"
+ }, this.zeroPadding = function (t, e) {
+ return t.length >= e ? t : new Array(e - t.length + 1).join("0") + t
+ }, this.getString = function () {
+ return this.s
+ }, this.setString = function (t) {
+ this.hTLV = null, this.isModified = !0, this.s = t, this.hV = stohex(t)
+ }, this.setByDateValue = function (t, e, i, r, n, s) {
+ var o = new Date(Date.UTC(t, e - 1, i, r, n, s, 0));
+ this.setByDate(o)
+ }, this.getFreshValueHex = function () {
+ return this.hV
+ }
+ }, W.lang.extend(tt.asn1.DERAbstractTime, tt.asn1.ASN1Object), tt.asn1.DERAbstractStructured = function (t) {
+ tt.asn1.DERAbstractString.superclass.constructor.call(this), this.setByASN1ObjectArray = function (t) {
+ this.hTLV = null, this.isModified = !0, this.asn1Array = t
+ }, this.appendASN1Object = function (t) {
+ this.hTLV = null, this.isModified = !0, this.asn1Array.push(t)
+ }, this.asn1Array = new Array, void 0 !== t && void 0 !== t.array && (this.asn1Array = t.array)
+ }, W.lang.extend(tt.asn1.DERAbstractStructured, tt.asn1.ASN1Object), tt.asn1.DERBoolean = function () {
+ tt.asn1.DERBoolean.superclass.constructor.call(this), this.hT = "01", this.hTLV = "0101ff"
+ }, W.lang.extend(tt.asn1.DERBoolean, tt.asn1.ASN1Object), tt.asn1.DERInteger = function (t) {
+ tt.asn1.DERInteger.superclass.constructor.call(this), this.hT = "02", this.setByBigInteger = function (t) {
+ this.hTLV = null, this.isModified = !0, this.hV = tt.asn1.ASN1Util.bigIntToMinTwosComplementsHex(t)
+ }, this.setByInteger = function (t) {
+ var e = new B(String(t), 10);
+ this.setByBigInteger(e)
+ }, this.setValueHex = function (t) {
+ this.hV = t
+ }, this.getFreshValueHex = function () {
+ return this.hV
+ }, void 0 !== t && (void 0 !== t.bigint ? this.setByBigInteger(t.bigint) : void 0 !== t.int ? this.setByInteger(t.int) : "number" == typeof t ? this.setByInteger(t) : void 0 !== t.hex && this.setValueHex(t.hex))
+ }, W.lang.extend(tt.asn1.DERInteger, tt.asn1.ASN1Object), tt.asn1.DERBitString = function (t) {
+ if (void 0 !== t && void 0 !== t.obj) {
+ var e = tt.asn1.ASN1Util.newObject(t.obj);
+ t.hex = "00" + e.getEncodedHex()
+ }
+ tt.asn1.DERBitString.superclass.constructor.call(this), this.hT = "03", this.setHexValueIncludingUnusedBits = function (t) {
+ this.hTLV = null, this.isModified = !0, this.hV = t
+ }, this.setUnusedBitsAndHexValue = function (t, e) {
+ if (t < 0 || 7 < t) throw"unused bits shall be from 0 to 7: u = " + t;
+ var i = "0" + t;
+ this.hTLV = null, this.isModified = !0, this.hV = i + e
+ }, this.setByBinaryString = function (t) {
+ var e = 8 - (t = t.replace(/0+$/, "")).length % 8;
+ 8 == e && (e = 0);
+ for (var i = 0; i <= e; i++) t += "0";
+ var r = "";
+ for (i = 0; i < t.length - 1; i += 8) {
+ var n = t.substr(i, 8), s = parseInt(n, 2).toString(16);
+ 1 == s.length && (s = "0" + s), r += s
+ }
+ this.hTLV = null, this.isModified = !0, this.hV = "0" + e + r
+ }, this.setByBooleanArray = function (t) {
+ for (var e = "", i = 0; i < t.length; i++) 1 == t[i] ? e += "1" : e += "0";
+ this.setByBinaryString(e)
+ }, this.newFalseArray = function (t) {
+ for (var e = new Array(t), i = 0; i < t; i++) e[i] = !1;
+ return e
+ }, this.getFreshValueHex = function () {
+ return this.hV
+ }, void 0 !== t && ("string" == typeof t && t.toLowerCase().match(/^[0-9a-f]+$/) ? this.setHexValueIncludingUnusedBits(t) : void 0 !== t.hex ? this.setHexValueIncludingUnusedBits(t.hex) : void 0 !== t.bin ? this.setByBinaryString(t.bin) : void 0 !== t.array && this.setByBooleanArray(t.array))
+ }, W.lang.extend(tt.asn1.DERBitString, tt.asn1.ASN1Object), tt.asn1.DEROctetString = function (t) {
+ if (void 0 !== t && void 0 !== t.obj) {
+ var e = tt.asn1.ASN1Util.newObject(t.obj);
+ t.hex = e.getEncodedHex()
+ }
+ tt.asn1.DEROctetString.superclass.constructor.call(this, t), this.hT = "04"
+ }, W.lang.extend(tt.asn1.DEROctetString, tt.asn1.DERAbstractString), tt.asn1.DERNull = function () {
+ tt.asn1.DERNull.superclass.constructor.call(this), this.hT = "05", this.hTLV = "0500"
+ }, W.lang.extend(tt.asn1.DERNull, tt.asn1.ASN1Object), tt.asn1.DERObjectIdentifier = function (t) {
+ var e = function (t) {
+ var e = t.toString(16);
+ return 1 == e.length && (e = "0" + e), e
+ }, i = function (t) {
+ var i = "", r = new B(t, 10).toString(2), n = 7 - r.length % 7;
+ 7 == n && (n = 0);
+ for (var s = "", o = 0; o < n; o++) s += "0";
+ for (r = s + r, o = 0; o < r.length - 1; o += 7) {
+ var h = r.substr(o, 7);
+ o != r.length - 7 && (h = "1" + h), i += e(parseInt(h, 2))
+ }
+ return i
+ };
+ tt.asn1.DERObjectIdentifier.superclass.constructor.call(this), this.hT = "06", this.setValueHex = function (t) {
+ this.hTLV = null, this.isModified = !0, this.s = null, this.hV = t
+ }, this.setValueOidString = function (t) {
+ if (!t.match(/^[0-9.]+$/)) throw"malformed oid string: " + t;
+ var r = "", n = t.split("."), s = 40 * parseInt(n[0]) + parseInt(n[1]);
+ r += e(s), n.splice(0, 2);
+ for (var o = 0; o < n.length; o++) r += i(n[o]);
+ this.hTLV = null, this.isModified = !0, this.s = null, this.hV = r
+ }, this.setValueName = function (t) {
+ var e = tt.asn1.x509.OID.name2oid(t);
+ if ("" === e) throw"DERObjectIdentifier oidName undefined: " + t;
+ this.setValueOidString(e)
+ }, this.getFreshValueHex = function () {
+ return this.hV
+ }, void 0 !== t && ("string" == typeof t ? t.match(/^[0-2].[0-9.]+$/) ? this.setValueOidString(t) : this.setValueName(t) : void 0 !== t.oid ? this.setValueOidString(t.oid) : void 0 !== t.hex ? this.setValueHex(t.hex) : void 0 !== t.name && this.setValueName(t.name))
+ }, W.lang.extend(tt.asn1.DERObjectIdentifier, tt.asn1.ASN1Object), tt.asn1.DEREnumerated = function (t) {
+ tt.asn1.DEREnumerated.superclass.constructor.call(this), this.hT = "0a", this.setByBigInteger = function (t) {
+ this.hTLV = null, this.isModified = !0, this.hV = tt.asn1.ASN1Util.bigIntToMinTwosComplementsHex(t)
+ }, this.setByInteger = function (t) {
+ var e = new B(String(t), 10);
+ this.setByBigInteger(e)
+ }, this.setValueHex = function (t) {
+ this.hV = t
+ }, this.getFreshValueHex = function () {
+ return this.hV
+ }, void 0 !== t && (void 0 !== t.int ? this.setByInteger(t.int) : "number" == typeof t ? this.setByInteger(t) : void 0 !== t.hex && this.setValueHex(t.hex))
+ }, W.lang.extend(tt.asn1.DEREnumerated, tt.asn1.ASN1Object), tt.asn1.DERUTF8String = function (t) {
+ tt.asn1.DERUTF8String.superclass.constructor.call(this, t), this.hT = "0c"
+ }, W.lang.extend(tt.asn1.DERUTF8String, tt.asn1.DERAbstractString), tt.asn1.DERNumericString = function (t) {
+ tt.asn1.DERNumericString.superclass.constructor.call(this, t), this.hT = "12"
+ }, W.lang.extend(tt.asn1.DERNumericString, tt.asn1.DERAbstractString), tt.asn1.DERPrintableString = function (t) {
+ tt.asn1.DERPrintableString.superclass.constructor.call(this, t), this.hT = "13"
+ }, W.lang.extend(tt.asn1.DERPrintableString, tt.asn1.DERAbstractString), tt.asn1.DERTeletexString = function (t) {
+ tt.asn1.DERTeletexString.superclass.constructor.call(this, t), this.hT = "14"
+ }, W.lang.extend(tt.asn1.DERTeletexString, tt.asn1.DERAbstractString), tt.asn1.DERIA5String = function (t) {
+ tt.asn1.DERIA5String.superclass.constructor.call(this, t), this.hT = "16"
+ }, W.lang.extend(tt.asn1.DERIA5String, tt.asn1.DERAbstractString), tt.asn1.DERUTCTime = function (t) {
+ tt.asn1.DERUTCTime.superclass.constructor.call(this, t), this.hT = "17", this.setByDate = function (t) {
+ this.hTLV = null, this.isModified = !0, this.date = t, this.s = this.formatDate(this.date, "utc"), this.hV = stohex(this.s)
+ }, this.getFreshValueHex = function () {
+ return void 0 === this.date && void 0 === this.s && (this.date = new Date, this.s = this.formatDate(this.date, "utc"), this.hV = stohex(this.s)), this.hV
+ }, void 0 !== t && (void 0 !== t.str ? this.setString(t.str) : "string" == typeof t && t.match(/^[0-9]{12}Z$/) ? this.setString(t) : void 0 !== t.hex ? this.setStringHex(t.hex) : void 0 !== t.date && this.setByDate(t.date))
+ }, W.lang.extend(tt.asn1.DERUTCTime, tt.asn1.DERAbstractTime), tt.asn1.DERGeneralizedTime = function (t) {
+ tt.asn1.DERGeneralizedTime.superclass.constructor.call(this, t), this.hT = "18", this.withMillis = !1, this.setByDate = function (t) {
+ this.hTLV = null, this.isModified = !0, this.date = t, this.s = this.formatDate(this.date, "gen", this.withMillis), this.hV = stohex(this.s)
+ }, this.getFreshValueHex = function () {
+ return void 0 === this.date && void 0 === this.s && (this.date = new Date, this.s = this.formatDate(this.date, "gen", this.withMillis), this.hV = stohex(this.s)), this.hV
+ }, void 0 !== t && (void 0 !== t.str ? this.setString(t.str) : "string" == typeof t && t.match(/^[0-9]{14}Z$/) ? this.setString(t) : void 0 !== t.hex ? this.setStringHex(t.hex) : void 0 !== t.date && this.setByDate(t.date), !0 === t.millis && (this.withMillis = !0))
+ }, W.lang.extend(tt.asn1.DERGeneralizedTime, tt.asn1.DERAbstractTime), tt.asn1.DERSequence = function (t) {
+ tt.asn1.DERSequence.superclass.constructor.call(this, t), this.hT = "30", this.getFreshValueHex = function () {
+ for (var t = "", e = 0; e < this.asn1Array.length; e++) t += this.asn1Array[e].getEncodedHex();
+ return this.hV = t, this.hV
+ }
+ }, W.lang.extend(tt.asn1.DERSequence, tt.asn1.DERAbstractStructured), tt.asn1.DERSet = function (t) {
+ tt.asn1.DERSet.superclass.constructor.call(this, t), this.hT = "31", this.sortFlag = !0, this.getFreshValueHex = function () {
+ for (var t = new Array, e = 0; e < this.asn1Array.length; e++) {
+ var i = this.asn1Array[e];
+ t.push(i.getEncodedHex())
+ }
+ return 1 == this.sortFlag && t.sort(), this.hV = t.join(""), this.hV
+ }, void 0 !== t && void 0 !== t.sortflag && 0 == t.sortflag && (this.sortFlag = !1)
+ }, W.lang.extend(tt.asn1.DERSet, tt.asn1.DERAbstractStructured), tt.asn1.DERTaggedObject = function (t) {
+ tt.asn1.DERTaggedObject.superclass.constructor.call(this), this.hT = "a0", this.hV = "", this.isExplicit = !0, this.asn1Object = null, this.setASN1Object = function (t, e, i) {
+ this.hT = e, this.isExplicit = t, this.asn1Object = i, this.isExplicit ? (this.hV = this.asn1Object.getEncodedHex(), this.hTLV = null, this.isModified = !0) : (this.hV = null, this.hTLV = i.getEncodedHex(), this.hTLV = this.hTLV.replace(/^../, e), this.isModified = !1)
+ }, this.getFreshValueHex = function () {
+ return this.hV
+ }, void 0 !== t && (void 0 !== t.tag && (this.hT = t.tag), void 0 !== t.explicit && (this.isExplicit = t.explicit), void 0 !== t.obj && (this.asn1Object = t.obj, this.setASN1Object(this.isExplicit, this.hT, this.asn1Object)))
+ }, W.lang.extend(tt.asn1.DERTaggedObject, tt.asn1.ASN1Object);
+ var et, it = (et = function (t, e) {
+ return (et = Object.setPrototypeOf || {__proto__: []} instanceof Array && function (t, e) {
+ t.__proto__ = e
+ } || function (t, e) {
+ for (var i in e) Object.prototype.hasOwnProperty.call(e, i) && (t[i] = e[i])
+ })(t, e)
+ }, function (t, e) {
+ function i() {
+ this.constructor = t
+ }
+
+ et(t, e), t.prototype = null === e ? Object.create(e) : (i.prototype = e.prototype, new i)
+ }), rt = function (t) {
+ function e(i) {
+ var r = t.call(this) || this;
+ return i && ("string" == typeof i ? r.parseKey(i) : (e.hasPrivateKeyProperty(i) || e.hasPublicKeyProperty(i)) && r.parsePropertiesFrom(i)), r
+ }
+
+ return it(e, t), e.prototype.parseKey = function (t) {
+ try {
+ var e = 0, i = 0, r = /^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/.test(t) ? function (t) {
+ var e;
+ if (void 0 === c) {
+ var i = "0123456789ABCDEF", r = " \f\n\r\t \u2028\u2029";
+ for (c = {}, e = 0; e < 16; ++e) c[i.charAt(e)] = e;
+ for (i = i.toLowerCase(), e = 10; e < 16; ++e) c[i.charAt(e)] = e;
+ for (e = 0; e < r.length; ++e) c[r.charAt(e)] = -1
+ }
+ var n = [], s = 0, o = 0;
+ for (e = 0; e < t.length; ++e) {
+ var h = t.charAt(e);
+ if ("=" == h) break;
+ if (-1 != (h = c[h])) {
+ if (void 0 === h) throw new Error("Illegal character at offset " + e);
+ s |= h, ++o >= 2 ? (n[n.length] = s, s = 0, o = 0) : s <<= 4
+ }
+ }
+ if (o) throw new Error("Hex encoding incomplete: 4 bits missing");
+ return n
+ }(t) : d.unarmor(t), n = w.decode(r);
+ if (3 === n.sub.length && (n = n.sub[2].sub[0]), 9 === n.sub.length) {
+ e = n.sub[1].getHexStringValue(), this.n = P(e, 16), i = n.sub[2].getHexStringValue(), this.e = parseInt(i, 16);
+ var s = n.sub[3].getHexStringValue();
+ this.d = P(s, 16);
+ var o = n.sub[4].getHexStringValue();
+ this.p = P(o, 16);
+ var h = n.sub[5].getHexStringValue();
+ this.q = P(h, 16);
+ var a = n.sub[6].getHexStringValue();
+ this.dmp1 = P(a, 16);
+ var u = n.sub[7].getHexStringValue();
+ this.dmq1 = P(u, 16);
+ var f = n.sub[8].getHexStringValue();
+ this.coeff = P(f, 16)
+ } else {
+ if (2 !== n.sub.length) return !1;
+ var l = n.sub[1].sub[0];
+ e = l.sub[0].getHexStringValue(), this.n = P(e, 16), i = l.sub[1].getHexStringValue(), this.e = parseInt(i, 16)
+ }
+ return !0
+ } catch (t) {
+ return !1
+ }
+ }, e.prototype.getPrivateBaseKey = function () {
+ var t = {array: [new tt.asn1.DERInteger({int: 0}), new tt.asn1.DERInteger({bigint: this.n}), new tt.asn1.DERInteger({int: this.e}), new tt.asn1.DERInteger({bigint: this.d}), new tt.asn1.DERInteger({bigint: this.p}), new tt.asn1.DERInteger({bigint: this.q}), new tt.asn1.DERInteger({bigint: this.dmp1}), new tt.asn1.DERInteger({bigint: this.dmq1}), new tt.asn1.DERInteger({bigint: this.coeff})]};
+ return new tt.asn1.DERSequence(t).getEncodedHex()
+ }, e.prototype.getPrivateBaseKeyB64 = function () {
+ return l(this.getPrivateBaseKey())
+ }, e.prototype.getPublicBaseKey = function () {
+ var t = new tt.asn1.DERSequence({array: [new tt.asn1.DERObjectIdentifier({oid: "1.2.840.113549.1.1.1"}), new tt.asn1.DERNull]}),
+ e = new tt.asn1.DERSequence({array: [new tt.asn1.DERInteger({bigint: this.n}), new tt.asn1.DERInteger({int: this.e})]}),
+ i = new tt.asn1.DERBitString({hex: "00" + e.getEncodedHex()});
+ return new tt.asn1.DERSequence({array: [t, i]}).getEncodedHex()
+ }, e.prototype.getPublicBaseKeyB64 = function () {
+ return l(this.getPublicBaseKey())
+ }, e.wordwrap = function (t, e) {
+ if (!t) return t;
+ var i = "(.{1," + (e = e || 64) + "})( +|$\n?)|(.{1," + e + "})";
+ return t.match(RegExp(i, "g")).join("\n")
+ }, e.prototype.getPrivateKey = function () {
+ var t = "-----BEGIN RSA PRIVATE KEY-----\n";
+ return (t += e.wordwrap(this.getPrivateBaseKeyB64()) + "\n") + "-----END RSA PRIVATE KEY-----"
+ }, e.prototype.getPublicKey = function () {
+ var t = "-----BEGIN PUBLIC KEY-----\n";
+ return (t += e.wordwrap(this.getPublicBaseKeyB64()) + "\n") + "-----END PUBLIC KEY-----"
+ }, e.hasPublicKeyProperty = function (t) {
+ return (t = t || {}).hasOwnProperty("n") && t.hasOwnProperty("e")
+ }, e.hasPrivateKeyProperty = function (t) {
+ return (t = t || {}).hasOwnProperty("n") && t.hasOwnProperty("e") && t.hasOwnProperty("d") && t.hasOwnProperty("p") && t.hasOwnProperty("q") && t.hasOwnProperty("dmp1") && t.hasOwnProperty("dmq1") && t.hasOwnProperty("coeff")
+ }, e.prototype.parsePropertiesFrom = function (t) {
+ this.n = t.n, this.e = t.e, t.hasOwnProperty("d") && (this.d = t.d, this.p = t.p, this.q = t.q, this.dmp1 = t.dmp1, this.dmq1 = t.dmq1, this.coeff = t.coeff)
+ }, e
+ }(X);
+ const nt = function () {
+ function t(t) {
+ t = t || {}, this.default_key_size = t.default_key_size ? parseInt(t.default_key_size, 10) : 1024, this.default_public_exponent = t.default_public_exponent || "010001", this.log = t.log || !1, this.key = null
+ }
+
+ return t.prototype.setKey = function (t) {
+ this.log && this.key && console.warn("A key was already set, overriding existing."), this.key = new rt(t)
+ }, t.prototype.setPrivateKey = function (t) {
+ this.setKey(t)
+ }, t.prototype.setPublicKey = function (t) {
+ this.setKey(t)
+ }, t.prototype.decrypt = function (t) {
+ try {
+ return this.getKey().decrypt(p(t))
+ } catch (t) {
+ return !1
+ }
+ }, t.prototype.encrypt = function (t) {
+ try {
+ return l(this.getKey().encrypt(t))
+ } catch (t) {
+ return !1
+ }
+ }, t.prototype.sign = function (t, e, i) {
+ try {
+ return l(this.getKey().sign(t, e, i))
+ } catch (t) {
+ return !1
+ }
+ }, t.prototype.verify = function (t, e, i) {
+ try {
+ return this.getKey().verify(t, p(e), i)
+ } catch (t) {
+ return !1
+ }
+ }, t.prototype.getKey = function (t) {
+ if (!this.key) {
+ if (this.key = new rt, t && "[object Function]" === {}.toString.call(t)) return void this.key.generateAsync(this.default_key_size, this.default_public_exponent, t);
+ this.key.generate(this.default_key_size, this.default_public_exponent)
+ }
+ return this.key
+ }, t.prototype.getPrivateKey = function () {
+ return this.getKey().getPrivateKey()
+ }, t.prototype.getPrivateKeyB64 = function () {
+ return this.getKey().getPrivateBaseKeyB64()
+ }, t.prototype.getPublicKey = function () {
+ return this.getKey().getPublicKey()
+ }, t.prototype.getPublicKeyB64 = function () {
+ return this.getKey().getPublicBaseKeyB64()
+ }, t.version = "3.0.0-rc.2", t
+ }()
+ }
+ }, e = {};
+
+ function i(r) {
+ if (e[r]) return e[r].exports;
+ var n = e[r] = {exports: {}};
+ return t[r](n, n.exports, i), n.exports
+ }
+
+ return i.d = (t, e) => {
+ for (var r in e) i.o(e, r) && !i.o(t, r) && Object.defineProperty(t, r, {enumerable: !0, get: e[r]})
+ }, i.o = (t, e) => Object.prototype.hasOwnProperty.call(t, e), i(771)
+ })().default
+}));
\ No newline at end of file
diff --git a/src/main/resources/static/js/lay-config.js b/src/main/resources/static/js/lay-config.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/common/common.js b/src/main/resources/static/js/lay-module/common/common.js
old mode 100644
new mode 100755
index 7fab1e5..79dae95
--- a/src/main/resources/static/js/lay-module/common/common.js
+++ b/src/main/resources/static/js/lay-module/common/common.js
@@ -54,7 +54,7 @@ layui.define(['form','table'], function (exports) { //提示:模块也可以
submitPostReq,
formListenFun: function (layFilter, type, path, resultId, reqType) {
form.on(`submit(${layFilter})`, function (data) {
- var value = data.field.content;
+ var value = data.field.payload;
// 定义白名单正则表达式
var whitelistRegex = /^[a-zA-Z0-9_\s]+$/;
diff --git a/src/main/resources/static/js/lay-module/echarts/echarts.js b/src/main/resources/static/js/lay-module/echarts/echarts.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/echarts/echartsTheme.js b/src/main/resources/static/js/lay-module/echarts/echartsTheme.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/iconPicker/iconPickerFa.js b/src/main/resources/static/js/lay-module/iconPicker/iconPickerFa.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/layarea/layarea.js b/src/main/resources/static/js/lay-module/layarea/layarea.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/layuimini/miniAdmin.js b/src/main/resources/static/js/lay-module/layuimini/miniAdmin.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/layuimini/miniMenu.js b/src/main/resources/static/js/lay-module/layuimini/miniMenu.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/layuimini/miniTab.js b/src/main/resources/static/js/lay-module/layuimini/miniTab.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/layuimini/miniTheme.js b/src/main/resources/static/js/lay-module/layuimini/miniTheme.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/layuimini/miniTongji.js b/src/main/resources/static/js/lay-module/layuimini/miniTongji.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/step-lay/step.css b/src/main/resources/static/js/lay-module/step-lay/step.css
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/step-lay/step.js b/src/main/resources/static/js/lay-module/step-lay/step.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/tableSelect/tableSelect.js b/src/main/resources/static/js/lay-module/tableSelect/tableSelect.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/treetable-lay/treetable.css b/src/main/resources/static/js/lay-module/treetable-lay/treetable.css
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/treetable-lay/treetable.js b/src/main/resources/static/js/lay-module/treetable-lay/treetable.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/wangEditor/fonts/w-e-icon.woff b/src/main/resources/static/js/lay-module/wangEditor/fonts/w-e-icon.woff
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/wangEditor/wangEditor.css b/src/main/resources/static/js/lay-module/wangEditor/wangEditor.css
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/wangEditor/wangEditor.js b/src/main/resources/static/js/lay-module/wangEditor/wangEditor.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/wangEditor/wangEditor.min.css b/src/main/resources/static/js/lay-module/wangEditor/wangEditor.min.css
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/wangEditor/wangEditor.min.js b/src/main/resources/static/js/lay-module/wangEditor/wangEditor.min.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/lay-module/wangEditor/wangEditor.min.js.map b/src/main/resources/static/js/lay-module/wangEditor/wangEditor.min.js.map
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/md5.min.js b/src/main/resources/static/js/md5.min.js
new file mode 100755
index 0000000..b74f9e7
--- /dev/null
+++ b/src/main/resources/static/js/md5.min.js
@@ -0,0 +1,109 @@
+/**
+ * [js-md5]{@link https://github.com/emn178/js-md5}
+ *
+ * @namespace md5
+ * @version 0.7.3
+ * @author Chen, Yi-Cyuan [emn178@gmail.com]
+ * @copyright Chen, Yi-Cyuan 2014-2017
+ * @license MIT
+ */
+!function () {
+ "use strict";
+
+ function t(t) {
+ if (t) d[0] = d[16] = d[1] = d[2] = d[3] = d[4] = d[5] = d[6] = d[7] = d[8] = d[9] = d[10] = d[11] = d[12] = d[13] = d[14] = d[15] = 0, this.blocks = d, this.buffer8 = l; else if (a) {
+ var r = new ArrayBuffer(68);
+ this.buffer8 = new Uint8Array(r), this.blocks = new Uint32Array(r)
+ } else this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ this.h0 = this.h1 = this.h2 = this.h3 = this.start = this.bytes = this.hBytes = 0, this.finalized = this.hashed = !1, this.first = !0
+ }
+
+ var r = "input is invalid type", e = "object" == typeof window, i = e ? window : {};
+ i.JS_MD5_NO_WINDOW && (e = !1);
+ var s = !e && "object" == typeof self,
+ h = !i.JS_MD5_NO_NODE_JS && "object" == typeof process && process.versions && process.versions.node;
+ h ? i = global : s && (i = self);
+ var f = !i.JS_MD5_NO_COMMON_JS && "object" == typeof module && module.exports,
+ o = "function" == typeof define && define.amd,
+ a = !i.JS_MD5_NO_ARRAY_BUFFER && "undefined" != typeof ArrayBuffer, n = "0123456789abcdef".split(""),
+ u = [128, 32768, 8388608, -2147483648], y = [0, 8, 16, 24],
+ c = ["hex", "array", "digest", "buffer", "arrayBuffer", "base64"],
+ p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""), d = [], l;
+ if (a) {
+ var A = new ArrayBuffer(68);
+ l = new Uint8Array(A), d = new Uint32Array(A)
+ }
+ !i.JS_MD5_NO_NODE_JS && Array.isArray || (Array.isArray = function (t) {
+ return "[object Array]" === Object.prototype.toString.call(t)
+ }), !a || !i.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW && ArrayBuffer.isView || (ArrayBuffer.isView = function (t) {
+ return "object" == typeof t && t.buffer && t.buffer.constructor === ArrayBuffer
+ });
+ var b = function (r) {
+ return function (e) {
+ return new t(!0).update(e)[r]()
+ }
+ }, v = function () {
+ var r = b("hex");
+ h && (r = w(r)), r.create = function () {
+ return new t
+ }, r.update = function (t) {
+ return r.create().update(t)
+ };
+ for (var e = 0; e < c.length; ++e) {
+ var i = c[e];
+ r[i] = b(i)
+ }
+ return r
+ }, w = function (t) {
+ var e = eval("require('crypto')"), i = eval("require('buffer').Buffer"), s = function (s) {
+ if ("string" == typeof s) return e.createHash("md5").update(s, "utf8").digest("hex");
+ if (null === s || void 0 === s) throw r;
+ return s.constructor === ArrayBuffer && (s = new Uint8Array(s)), Array.isArray(s) || ArrayBuffer.isView(s) || s.constructor === i ? e.createHash("md5").update(new i(s)).digest("hex") : t(s)
+ };
+ return s
+ };
+ t.prototype.update = function (t) {
+ if (!this.finalized) {
+ var e, i = typeof t;
+ if ("string" !== i) {
+ if ("object" !== i) throw r;
+ if (null === t) throw r;
+ if (a && t.constructor === ArrayBuffer) t = new Uint8Array(t); else if (!(Array.isArray(t) || a && ArrayBuffer.isView(t))) throw r;
+ e = !0
+ }
+ for (var s, h, f = 0, o = t.length, n = this.blocks, u = this.buffer8; f < o;) {
+ if (this.hashed && (this.hashed = !1, n[0] = n[16], n[16] = n[1] = n[2] = n[3] = n[4] = n[5] = n[6] = n[7] = n[8] = n[9] = n[10] = n[11] = n[12] = n[13] = n[14] = n[15] = 0), e) if (a) for (h = this.start; f < o && h < 64; ++f) u[h++] = t[f]; else for (h = this.start; f < o && h < 64; ++f) n[h >> 2] |= t[f] << y[3 & h++]; else if (a) for (h = this.start; f < o && h < 64; ++f) (s = t.charCodeAt(f)) < 128 ? u[h++] = s : s < 2048 ? (u[h++] = 192 | s >> 6, u[h++] = 128 | 63 & s) : s < 55296 || s >= 57344 ? (u[h++] = 224 | s >> 12, u[h++] = 128 | s >> 6 & 63, u[h++] = 128 | 63 & s) : (s = 65536 + ((1023 & s) << 10 | 1023 & t.charCodeAt(++f)), u[h++] = 240 | s >> 18, u[h++] = 128 | s >> 12 & 63, u[h++] = 128 | s >> 6 & 63, u[h++] = 128 | 63 & s); else for (h = this.start; f < o && h < 64; ++f) (s = t.charCodeAt(f)) < 128 ? n[h >> 2] |= s << y[3 & h++] : s < 2048 ? (n[h >> 2] |= (192 | s >> 6) << y[3 & h++], n[h >> 2] |= (128 | 63 & s) << y[3 & h++]) : s < 55296 || s >= 57344 ? (n[h >> 2] |= (224 | s >> 12) << y[3 & h++], n[h >> 2] |= (128 | s >> 6 & 63) << y[3 & h++], n[h >> 2] |= (128 | 63 & s) << y[3 & h++]) : (s = 65536 + ((1023 & s) << 10 | 1023 & t.charCodeAt(++f)), n[h >> 2] |= (240 | s >> 18) << y[3 & h++], n[h >> 2] |= (128 | s >> 12 & 63) << y[3 & h++], n[h >> 2] |= (128 | s >> 6 & 63) << y[3 & h++], n[h >> 2] |= (128 | 63 & s) << y[3 & h++]);
+ this.lastByteIndex = h, this.bytes += h - this.start, h >= 64 ? (this.start = h - 64, this.hash(), this.hashed = !0) : this.start = h
+ }
+ return this.bytes > 4294967295 && (this.hBytes += this.bytes / 4294967296 << 0, this.bytes = this.bytes % 4294967296), this
+ }
+ }, t.prototype.finalize = function () {
+ if (!this.finalized) {
+ this.finalized = !0;
+ var t = this.blocks, r = this.lastByteIndex;
+ t[r >> 2] |= u[3 & r], r >= 56 && (this.hashed || this.hash(), t[0] = t[16], t[16] = t[1] = t[2] = t[3] = t[4] = t[5] = t[6] = t[7] = t[8] = t[9] = t[10] = t[11] = t[12] = t[13] = t[14] = t[15] = 0), t[14] = this.bytes << 3, t[15] = this.hBytes << 3 | this.bytes >>> 29, this.hash()
+ }
+ }, t.prototype.hash = function () {
+ var t, r, e, i, s, h, f = this.blocks;
+ this.first ? r = ((r = ((t = ((t = f[0] - 680876937) << 7 | t >>> 25) - 271733879 << 0) ^ (e = ((e = (-271733879 ^ (i = ((i = (-1732584194 ^ 2004318071 & t) + f[1] - 117830708) << 12 | i >>> 20) + t << 0) & (-271733879 ^ t)) + f[2] - 1126478375) << 17 | e >>> 15) + i << 0) & (i ^ t)) + f[3] - 1316259209) << 22 | r >>> 10) + e << 0 : (t = this.h0, r = this.h1, e = this.h2, r = ((r += ((t = ((t += ((i = this.h3) ^ r & (e ^ i)) + f[0] - 680876936) << 7 | t >>> 25) + r << 0) ^ (e = ((e += (r ^ (i = ((i += (e ^ t & (r ^ e)) + f[1] - 389564586) << 12 | i >>> 20) + t << 0) & (t ^ r)) + f[2] + 606105819) << 17 | e >>> 15) + i << 0) & (i ^ t)) + f[3] - 1044525330) << 22 | r >>> 10) + e << 0), r = ((r += ((t = ((t += (i ^ r & (e ^ i)) + f[4] - 176418897) << 7 | t >>> 25) + r << 0) ^ (e = ((e += (r ^ (i = ((i += (e ^ t & (r ^ e)) + f[5] + 1200080426) << 12 | i >>> 20) + t << 0) & (t ^ r)) + f[6] - 1473231341) << 17 | e >>> 15) + i << 0) & (i ^ t)) + f[7] - 45705983) << 22 | r >>> 10) + e << 0, r = ((r += ((t = ((t += (i ^ r & (e ^ i)) + f[8] + 1770035416) << 7 | t >>> 25) + r << 0) ^ (e = ((e += (r ^ (i = ((i += (e ^ t & (r ^ e)) + f[9] - 1958414417) << 12 | i >>> 20) + t << 0) & (t ^ r)) + f[10] - 42063) << 17 | e >>> 15) + i << 0) & (i ^ t)) + f[11] - 1990404162) << 22 | r >>> 10) + e << 0, r = ((r += ((t = ((t += (i ^ r & (e ^ i)) + f[12] + 1804603682) << 7 | t >>> 25) + r << 0) ^ (e = ((e += (r ^ (i = ((i += (e ^ t & (r ^ e)) + f[13] - 40341101) << 12 | i >>> 20) + t << 0) & (t ^ r)) + f[14] - 1502002290) << 17 | e >>> 15) + i << 0) & (i ^ t)) + f[15] + 1236535329) << 22 | r >>> 10) + e << 0, r = ((r += ((i = ((i += (r ^ e & ((t = ((t += (e ^ i & (r ^ e)) + f[1] - 165796510) << 5 | t >>> 27) + r << 0) ^ r)) + f[6] - 1069501632) << 9 | i >>> 23) + t << 0) ^ t & ((e = ((e += (t ^ r & (i ^ t)) + f[11] + 643717713) << 14 | e >>> 18) + i << 0) ^ i)) + f[0] - 373897302) << 20 | r >>> 12) + e << 0, r = ((r += ((i = ((i += (r ^ e & ((t = ((t += (e ^ i & (r ^ e)) + f[5] - 701558691) << 5 | t >>> 27) + r << 0) ^ r)) + f[10] + 38016083) << 9 | i >>> 23) + t << 0) ^ t & ((e = ((e += (t ^ r & (i ^ t)) + f[15] - 660478335) << 14 | e >>> 18) + i << 0) ^ i)) + f[4] - 405537848) << 20 | r >>> 12) + e << 0, r = ((r += ((i = ((i += (r ^ e & ((t = ((t += (e ^ i & (r ^ e)) + f[9] + 568446438) << 5 | t >>> 27) + r << 0) ^ r)) + f[14] - 1019803690) << 9 | i >>> 23) + t << 0) ^ t & ((e = ((e += (t ^ r & (i ^ t)) + f[3] - 187363961) << 14 | e >>> 18) + i << 0) ^ i)) + f[8] + 1163531501) << 20 | r >>> 12) + e << 0, r = ((r += ((i = ((i += (r ^ e & ((t = ((t += (e ^ i & (r ^ e)) + f[13] - 1444681467) << 5 | t >>> 27) + r << 0) ^ r)) + f[2] - 51403784) << 9 | i >>> 23) + t << 0) ^ t & ((e = ((e += (t ^ r & (i ^ t)) + f[7] + 1735328473) << 14 | e >>> 18) + i << 0) ^ i)) + f[12] - 1926607734) << 20 | r >>> 12) + e << 0, r = ((r += ((h = (i = ((i += ((s = r ^ e) ^ (t = ((t += (s ^ i) + f[5] - 378558) << 4 | t >>> 28) + r << 0)) + f[8] - 2022574463) << 11 | i >>> 21) + t << 0) ^ t) ^ (e = ((e += (h ^ r) + f[11] + 1839030562) << 16 | e >>> 16) + i << 0)) + f[14] - 35309556) << 23 | r >>> 9) + e << 0, r = ((r += ((h = (i = ((i += ((s = r ^ e) ^ (t = ((t += (s ^ i) + f[1] - 1530992060) << 4 | t >>> 28) + r << 0)) + f[4] + 1272893353) << 11 | i >>> 21) + t << 0) ^ t) ^ (e = ((e += (h ^ r) + f[7] - 155497632) << 16 | e >>> 16) + i << 0)) + f[10] - 1094730640) << 23 | r >>> 9) + e << 0, r = ((r += ((h = (i = ((i += ((s = r ^ e) ^ (t = ((t += (s ^ i) + f[13] + 681279174) << 4 | t >>> 28) + r << 0)) + f[0] - 358537222) << 11 | i >>> 21) + t << 0) ^ t) ^ (e = ((e += (h ^ r) + f[3] - 722521979) << 16 | e >>> 16) + i << 0)) + f[6] + 76029189) << 23 | r >>> 9) + e << 0, r = ((r += ((h = (i = ((i += ((s = r ^ e) ^ (t = ((t += (s ^ i) + f[9] - 640364487) << 4 | t >>> 28) + r << 0)) + f[12] - 421815835) << 11 | i >>> 21) + t << 0) ^ t) ^ (e = ((e += (h ^ r) + f[15] + 530742520) << 16 | e >>> 16) + i << 0)) + f[2] - 995338651) << 23 | r >>> 9) + e << 0, r = ((r += ((i = ((i += (r ^ ((t = ((t += (e ^ (r | ~i)) + f[0] - 198630844) << 6 | t >>> 26) + r << 0) | ~e)) + f[7] + 1126891415) << 10 | i >>> 22) + t << 0) ^ ((e = ((e += (t ^ (i | ~r)) + f[14] - 1416354905) << 15 | e >>> 17) + i << 0) | ~t)) + f[5] - 57434055) << 21 | r >>> 11) + e << 0, r = ((r += ((i = ((i += (r ^ ((t = ((t += (e ^ (r | ~i)) + f[12] + 1700485571) << 6 | t >>> 26) + r << 0) | ~e)) + f[3] - 1894986606) << 10 | i >>> 22) + t << 0) ^ ((e = ((e += (t ^ (i | ~r)) + f[10] - 1051523) << 15 | e >>> 17) + i << 0) | ~t)) + f[1] - 2054922799) << 21 | r >>> 11) + e << 0, r = ((r += ((i = ((i += (r ^ ((t = ((t += (e ^ (r | ~i)) + f[8] + 1873313359) << 6 | t >>> 26) + r << 0) | ~e)) + f[15] - 30611744) << 10 | i >>> 22) + t << 0) ^ ((e = ((e += (t ^ (i | ~r)) + f[6] - 1560198380) << 15 | e >>> 17) + i << 0) | ~t)) + f[13] + 1309151649) << 21 | r >>> 11) + e << 0, r = ((r += ((i = ((i += (r ^ ((t = ((t += (e ^ (r | ~i)) + f[4] - 145523070) << 6 | t >>> 26) + r << 0) | ~e)) + f[11] - 1120210379) << 10 | i >>> 22) + t << 0) ^ ((e = ((e += (t ^ (i | ~r)) + f[2] + 718787259) << 15 | e >>> 17) + i << 0) | ~t)) + f[9] - 343485551) << 21 | r >>> 11) + e << 0, this.first ? (this.h0 = t + 1732584193 << 0, this.h1 = r - 271733879 << 0, this.h2 = e - 1732584194 << 0, this.h3 = i + 271733878 << 0, this.first = !1) : (this.h0 = this.h0 + t << 0, this.h1 = this.h1 + r << 0, this.h2 = this.h2 + e << 0, this.h3 = this.h3 + i << 0)
+ }, t.prototype.hex = function () {
+ this.finalize();
+ var t = this.h0, r = this.h1, e = this.h2, i = this.h3;
+ return n[t >> 4 & 15] + n[15 & t] + n[t >> 12 & 15] + n[t >> 8 & 15] + n[t >> 20 & 15] + n[t >> 16 & 15] + n[t >> 28 & 15] + n[t >> 24 & 15] + n[r >> 4 & 15] + n[15 & r] + n[r >> 12 & 15] + n[r >> 8 & 15] + n[r >> 20 & 15] + n[r >> 16 & 15] + n[r >> 28 & 15] + n[r >> 24 & 15] + n[e >> 4 & 15] + n[15 & e] + n[e >> 12 & 15] + n[e >> 8 & 15] + n[e >> 20 & 15] + n[e >> 16 & 15] + n[e >> 28 & 15] + n[e >> 24 & 15] + n[i >> 4 & 15] + n[15 & i] + n[i >> 12 & 15] + n[i >> 8 & 15] + n[i >> 20 & 15] + n[i >> 16 & 15] + n[i >> 28 & 15] + n[i >> 24 & 15]
+ }, t.prototype.toString = t.prototype.hex, t.prototype.digest = function () {
+ this.finalize();
+ var t = this.h0, r = this.h1, e = this.h2, i = this.h3;
+ return [255 & t, t >> 8 & 255, t >> 16 & 255, t >> 24 & 255, 255 & r, r >> 8 & 255, r >> 16 & 255, r >> 24 & 255, 255 & e, e >> 8 & 255, e >> 16 & 255, e >> 24 & 255, 255 & i, i >> 8 & 255, i >> 16 & 255, i >> 24 & 255]
+ }, t.prototype.array = t.prototype.digest, t.prototype.arrayBuffer = function () {
+ this.finalize();
+ var t = new ArrayBuffer(16), r = new Uint32Array(t);
+ return r[0] = this.h0, r[1] = this.h1, r[2] = this.h2, r[3] = this.h3, t
+ }, t.prototype.buffer = t.prototype.arrayBuffer, t.prototype.base64 = function () {
+ for (var t, r, e, i = "", s = this.array(), h = 0; h < 15;) t = s[h++], r = s[h++], e = s[h++], i += p[t >>> 2] + p[63 & (t << 4 | r >>> 4)] + p[63 & (r << 2 | e >>> 6)] + p[63 & e];
+ return t = s[h], i += p[t >>> 2] + p[t << 4 & 63] + "=="
+ };
+ var _ = v();
+ f ? module.exports = _ : (i.md5 = _, o && define(function () {
+ return _
+ }))
+}();
\ No newline at end of file
diff --git a/src/main/resources/static/js/sec-tip.js b/src/main/resources/static/js/sec-tip.js
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/js/staticcode.js b/src/main/resources/static/js/staticcode.js
old mode 100644
new mode 100755
index 9a33892..d50001f
--- a/src/main/resources/static/js/staticcode.js
+++ b/src/main/resources/static/js/staticcode.js
@@ -4,10 +4,9 @@
* @email: whgojp@foxmail.com
* @Date: 2024/5/19 19:03
*/
-const vul1ReflectRaw = "// 原生漏洞环境,未加任何过滤,Controller接口返回Json类型结果\n" +
- "@RequestMapping(\"/vul1ReflectRaw\") // 可接收各种请求类型\n" +
- "public R vul1ReflectRaw(@ApiParam(name = \"type\", value = \"请求参数\", required = true) @RequestParam String content) {\n" +
- " return R.ok(content);\n" +
+const vul1ReflectRaw = "// 原生漏洞场景,未加任何过滤,Controller接口返回JSON类型结果\n" +
+ "public R vul1(String payload) {\n" +
+ " return R.ok(payload);\n" +
"}\n" +
"// R 是对返回结果的封装工具util\n" +
"// 返回结果:\n" +
@@ -15,30 +14,28 @@ const vul1ReflectRaw = "// 原生漏洞环境,未加任何过滤,Controller接
"// \"msg\": \"\",\n" +
"// \"code\": 0\n" +
"// }\n" +
- "// payload在json中是不会触发xss的 需要解析到页面中\n" +
+ "// JSON响应本身通常不会直接执行脚本;前端若把字段用innerHTML等方式写入页面,才会触发XSS\n" +
"\n" +
- "// 原生漏洞环境,未加任何过滤,Controller接口返回String类型结果\n" +
- "@GetMapping(\"/vul1ReflectRawString\")\n" +
- "public String vul1ReflectRawString(@ApiParam(name = \"type\", value = \"请求参数\", required = true) @RequestParam String content) {\n" +
- " return content;\n" +
+ "// 原生漏洞场景,未加任何过滤,Controller接口返回String类型结果\n" +
+ "public String vul2(String payload) {\n" +
+ " return payload;\n" +
"}"
const vul2ReflectContentType = "// Tomcat内置HttpServletResponse,Content-Type导致反射XSS\n" +
- "@GetMapping(\"/vul2ReflectContentType\")\n" +
- "public void vul2ReflectContentType(@ApiParam(name = \"type\", value = \"类型\", required = true) @RequestParam String type, @ApiParam(name = \"content\", value = \"请求参数\", required = true) @RequestParam String content, HttpServletResponse response) {\n" +
+ "public void vul3(String type,String payload, HttpServletResponse response) {\n" +
" switch (type) {\n" +
" case \"html\":\n" +
- " response.getWriter().print(content);\n" +
+ " response.getWriter().print(payload);\n" +
" response.setContentType(\"text/html;charset=utf-8\");\n" +
" response.getWriter().flush();\n" +
" break;\n" +
" case \"plain\":\n" +
- " response.getWriter().print(content);\n" +
+ " response.getWriter().print(payload);\n" +
" response.setContentType(\"text/plain;charset=utf-8\");\n" +
" response.getWriter().flush();\n" +
" ...\n" +
" }\n" +
"}"
-const safe1CheckUserInput = "// 对用户输入的数据进行验证和过滤,确保不包含恶意代码。使用白名单过滤,只允许特定类型的输入,如纯文本或指定格式的数据\n" +
+const safe1CheckUserInput = "// 使用白名单限制输入格式,适合约束字段类型;最终仍需根据输出位置进行上下文编码\n" +
"// 前端校验代码\n" +
"var whitelistRegex = /^[a-zA-Z0-9_\\s]+$/;\n" +
"\n" +
@@ -54,58 +51,56 @@ const safe1CheckUserInput = "// 对用户输入的数据进行验证和过滤,
"private static final String WHITELIST_REGEX = \"^[a-zA-Z0-9_\\\\s]+$\";\n" +
"private static final Pattern pattern = Pattern.compile(WHITELIST_REGEX);\n" +
"\n" +
- "Matcher matcher = pattern.matcher(content);\n" +
+ "Matcher matcher = pattern.matcher(payload);\n" +
"if (matcher.matches()){\n" +
- " return R.ok(content);\n" +
+ " return R.ok(payload);\n" +
"}else return R.error(\"输入内容包含非法字符,请检查输入\");"
-const safe2CSP = "// 内容安全策略(Content Security Policy)是一种由浏览器实施的安全机制,旨在减少和防范跨站脚本攻击(XSS)等安全威胁。它通过允许网站管理员定义哪些内容来源是可信任的,从而防止恶意内容的加载和执行\n" +
+const safe2CSP = "// 内容安全策略(Content Security Policy)是浏览器实施的额外防护层,可降低恶意脚本加载和执行风险,但不能替代输出编码与安全DOM/模板用法\n" +
"// 前端Meta配置\n" +
" \n" +
"\n" +
"\n" +
"// 后端Header配置\n" +
- "@RequestMapping(\"/safe2CSP\")\n" +
- "public String safe2CSP(@ApiParam(name = \"content\", value = \"请求参数\", required = true) @RequestParam String content,HttpServletResponse response) {\n" +
- " response.setHeader(\"Content-Security-Policy\",\"default-src self\");\n" +
- " return content;\n" +
+ "public String safe2(String payload,HttpServletResponse response) {\n" +
+ " response.setHeader(\"Content-Security-Policy\", \"default-src 'self'; script-src 'self'\");\n" +
+ " response.setHeader(\"Content-Security-Policy-Report-Only\", \"default-src 'self'; report-uri /xss/reflect/csp-report-endpoint\");\n" +
+ " return payload;\n" +
"}"
-const safe3EntityEscape = '// 特殊字符实体转义是一种将HTML中的特殊字符转换为预定义实体表示的过程\n' +
- '// 这种转义是为了确保在HTML页面中正确显示特定字符,同时避免它们被浏览器误解为HTML标签或JavaScript代码的一部分,从而导致页面结构混乱或安全漏洞\n' +
- '@RequestMapping("/safe3EntityEscape")\n' +
- 'public R safe3EntityEscape(@ApiParam(name = "type", value = "类型", required = true) @RequestParam String type, @ApiParam(name = "content", value = "请求参数", required = true) @RequestParam String content) {\n' +
+const safe3EntityEscape = '// HTML正文输出编码会将特殊字符转换为HTML实体,避免浏览器把不可信数据解析为标签或脚本\n' +
+ '// 注意:HTML属性、URL、JavaScript字符串、CSS等不同上下文需要使用不同的编码或白名单校验策略\n' +
+ 'public R safe3(@ApiParam(String type, String payload) {\n' +
' String filterContented = "";\n' +
' switch (type){\n' +
' case "manual":\n' +
- ' content = StringUtils.replace(content, "&", "&");\n' +
- ' content = StringUtils.replace(content, "<", "<");\n' +
- ' content = StringUtils.replace(content, ">", ">");\n' +
- ' content = StringUtils.replace(content, "\\"", """);\n' +
- ' content = StringUtils.replace(content, "\'", "'");\n' +
- ' content = StringUtils.replace(content, "/", "/");\n' +
- ' filterContented = content;\n' +
+ ' payload = StringUtils.replace(payload, "&", "&");\n' +
+ ' payload = StringUtils.replace(payload, "<", "<");\n' +
+ ' payload = StringUtils.replace(payload, ">", ">");\n' +
+ ' payload = StringUtils.replace(payload, "\\"", """);\n' +
+ ' payload = StringUtils.replace(payload, "\'", "'");\n' +
+ ' payload = StringUtils.replace(payload, "/", "/");\n' +
+ ' filterContented = payload;\n' +
' break;\n' +
' case "spring":\n' +
- ' filterContented = HtmlUtils.htmlEscape(content);\n' +
+ ' filterContented = HtmlUtils.htmlEscape(payload);\n' +
' break;\n' +
' ...\n' +
' }\n' +
'}'
-const safe4HttpOnly = "// HttpOnly是HTTP响应头属性,用于增强Web应用程序安全性。它防止客户端脚本访问(只能通过http/https协议访问)带有HttpOnly标记的 cookie,从而减少跨站点脚本攻击(XSS)的风险。\n" +
+const safe4HttpOnly = "// HttpOnly可以阻止客户端脚本直接读取带有该属性的Cookie,降低XSS窃取Cookie的影响,但不能修复XSS本身\n" +
"// 单个接口配置\n" +
- "@RequestMapping(value = \"/safe4HttpOnly\", method = RequestMethod.GET)\n" +
- "public R safe4HttpOnly(@ApiParam(name = \"content\", value = \"请求参数\", required = true) String content, HttpServletRequest request,HttpServletResponse response) {\n" +
- " Cookie cookie = request.getCookies()[ueditor];\n" +
+ "public R safe4(String payload, HttpServletRequest request,HttpServletResponse response) {\n" +
+ " Cookie cookie = request.getCookies()[0];\n" +
" cookie.setHttpOnly(true); // 设置为 HttpOnly\n" +
" cookie.setMaxAge(600); // 这里设置生效时间为十分钟\n" +
" cookie.setPath(\"/\");\n" +
" response.addCookie(cookie);\n" +
- " return R.ok(content);\n" +
+ " return R.ok(payload);\n" +
"}\n" +
"\n" +
"// 全局配置\n" +
- "// ueditor、application.yml配置\n" +
+ "// application.yml配置\n" +
"server:\n" +
" servlet:\n" +
" session:\n" +
@@ -124,12 +119,11 @@ const safe4HttpOnly = "// HttpOnly是HTTP响应头属性,用于增强Web应用
" ...\n" +
"}"
-const vul1StoreRaw = "// 原生漏洞环境,未加任何过滤,将用户输入存储到数据库中\n" +
+const vul1StoreRaw = "// 原生漏洞场景,未加任何过滤,将用户输入和User-Agent持久化;后续页面不安全渲染时触发存储型XSS\n" +
"// Controller层\n" +
- "@RequestMapping(\"/vul1StoreRaw\")\n" +
- "public R vul1StoreRaw(@ApiParam(name = \"content\", value = \"请求参数\", required = true) @RequestParam String content,HttpServletRequest request) {\n" +
+ "public R vul(String payload,HttpServletRequest request) {\n" +
" String ua = request.getHeader(\"User-Agent\");\n" +
- " final int code = xssService.insertOne(content,ua);\n" +
+ " final int code = xssService.insertOne(payload,ua);\n" +
" ...\n" +
"}\n" +
"// Service层\n" +
@@ -146,7 +140,7 @@ const vul1StoreRaw = "// 原生漏洞环境,未加任何过滤,将用户输入
" values (#{content,jdbcType=VARCHAR},#{ua,jdbcType=VARCHAR}, #{date,jdbcType=VARCHAR})\n" +
" "
-const safe1StoreEntityEscape = "// 表格数据渲染\n" +
+const safe1StoreEntityEscape = "// 表格数据渲染:数据库仍保存原始值,输出到HTML页面前按HTML正文文本编码\n" +
"table.render({\n" +
"\t...\n" +
" cols: [\n" +
@@ -158,10 +152,13 @@ const safe1StoreEntityEscape = "// 表格数据渲染\n" +
" return escapeHtml(d.ua); \n" +
" }},\n" +
" \t...\n" +
- "// 方法一、HTML 实体转义函数\n" +
+ "// 方法一、HTML正文输出编码函数\n" +
"function escapeHtml(html) {\n" +
+ " if (html === null || html === undefined) {\n" +
+ " return '';\n" +
+ " }\n" +
" var text = document.createElement(\"textarea\");\n" +
- " text.textContent = html;\n" +
+ " text.textContent = String(html);\n" +
" return text.innerHTML;\n" +
"}\n" +
"// 方法二、JavaScript的文本节点\n" +
@@ -170,37 +167,87 @@ const safe1StoreEntityEscape = "// 表格数据渲染\n" +
"// 方法三、jQuery的text()方法\n" +
"$('#element').text(htmlContent);\n"
-const vul1DomRaw = "// innerHTML\n" +
+const vul1DomRaw = "// 1. innerHTML XSS\n" +
"form.on('submit(vul1-dom-raw)', function (data) {\n" +
" var userInput = document.getElementById('vul1-dom-raw-input').value;\n" +
" var outputDiv = document.getElementById('vul-dom-raw-result');\n" +
- " outputDiv.innerHTML = userInput;\n" +
+ " outputDiv.innerHTML = userInput; // 漏洞点:直接使用innerHTML插入用户输入\n" +
+ " return false;\n" +
+ "});\n" +
+ "\n" +
+ "// 2. LocalStorage XSS\n" +
+ "form.on('submit(vul3-dom-raw-submit)', function (data) {\n" +
+ " localStorage.setItem('vul4-dom-raw', document.getElementById('vul4-dom-raw-input').value);\n" +
+ " var storedData = localStorage.getItem('vul4-dom-raw');\n" +
+ " document.getElementById('vul-dom-raw-result').innerHTML = storedData; // 漏洞点:从存储读取后直接插入\n" +
" return false;\n" +
"});\n" +
"\n" +
- "// href跳转场景\n" +
+ "// 3. href跳转XSS\n" +
"var hash = location.hash;\n" +
"if(hash){\n" +
- " var url = hash.substring(ueditor);\n" +
+ " var url = hash.substring(1); // 去掉#号\n" +
" console.log(url);\n" +
- " location.href = url;\n" +
+ " location.href = url; // 漏洞点:直接使用hash部分作为跳转URL\n" +
"}\n" +
"\n" +
- "// DOM存储注入\n" +
- "form.on('submit(vul3-dom-raw-submit)', function (data) {\n" +
- " localStorage.setItem('vul4-dom-raw', document.getElementById('vul4-dom-raw-input').value);\n" +
- " var storedData = localStorage.getItem('vul4-dom-raw');\n" +
- " document.getElementById('vul-dom-raw-result').innerHTML = storedData;\n" +
+ "// 4. Location对象XSS\n" +
+ "form.on('submit(location-xss)', function(data) {\n" +
+ " var payload = data.field.locationPayload;\n" +
+ " window.location = payload; // 漏洞点:直接使用用户输入修改location\n" +
" return false;\n" +
- "})"
+ "});\n" +
+ "\n" +
+ "// 5. Eval执行XSS\n" +
+ "form.on('submit(eval-xss)', function(data) {\n" +
+ " var payload = data.field.evalPayload;\n" +
+ " eval(payload); // 漏洞点:直接执行用户输入的JavaScript代码\n" +
+ " return false;\n" +
+ "});\n" +
+ "\n" +
+ "// 6. Document对象XSS\n" +
+ "form.on('submit(document-write)', function(data) {\n" +
+ " var payload = data.field.documentPayload;\n" +
+ " document.write(payload); // 漏洞点:直接写入用户输入的HTML\n" +
+ " document.close();\n" +
+ " return false;\n" +
+ "});\n" +
+ "form.on('submit(document-domain)', function(data) {\n" +
+ " var payload = data.field.documentPayload;\n" +
+ " document.domain = payload; // 风险点:直接修改document.domain会放宽同源边界或造成异常行为\n" +
+ " return false;\n" +
+ "});"
-const vul1OtherUpload = "@RequestMapping(\"/vul1Upload\")\n" +
- "public String uploadFile(MultipartFile file, String suffix,String path) throws IOException {\n" +
+const safeDomCode = "// 1. 普通文本输出:使用textContent,不解析HTML\n" +
+ "document.getElementById('safe-dom-result').textContent = userInput;\n" +
"\n" +
- " String uploadFolderPath = sysConstant.getUploadFolder();\n" +
+ "// 2. URL跳转:校验协议白名单,拒绝javascript:、data:等危险协议\n" +
+ "var url = new URL(userInput, window.location.origin);\n" +
+ "var allowedProtocols = ['http:', 'https:'];\n" +
+ "if (allowedProtocols.indexOf(url.protocol) === -1) {\n" +
+ " throw new Error('dangerous protocol');\n" +
+ "}\n" +
"\n" +
- " try {\n" +
+ "// 3. 替代eval:使用命令白名单映射,而不是执行用户输入\n" +
+ "var actions = {\n" +
+ " showTime: function () { return new Date().toLocaleString(); },\n" +
+ " showLocation: function () { return window.location.pathname; }\n" +
+ "};\n" +
+ "var action = actions[userInput];\n" +
+ "if (action) {\n" +
+ " action();\n" +
+ "}\n" +
+ "\n" +
+ "// 4. DOM API:创建文本节点,不拼接HTML字符串\n" +
+ "var node = document.createTextNode(userInput);\n" +
+ "element.appendChild(node);\n" +
"\n" +
+ "// 如果业务必须展示富文本,应先使用白名单HTML净化库处理后再渲染\n"
+
+const vul1OtherUpload = "// 上传可被浏览器或预览服务解析的HTML/SVG/XML/PDF等文件,后续访问文件时可能触发XSS或内容安全问题\n" +
+ "public String uploadFile(MultipartFile file, String suffix,String path) throws IOException {\n" +
+ " String uploadFolderPath = sysConstant.getUploadFolder();\n" +
+ " try {\n" +
" String fileName = +DateUtil.current() + \".\"+suffix;\n" +
" String newFilePath = uploadFolderPath + \"/\" + fileName;\n" +
"\n" +
@@ -212,15 +259,14 @@ const vul1OtherUpload = "@RequestMapping(\"/vul1Upload\")\n" +
" log.info(\"文件上传失败\" + e.getMessage());\n" +
" return \"文件上传失败\" + e.getMessage();\n" +
" }\n" +
- "}\n"
+ "}"
-const vul2OtherTemplate = "@GetMapping(\"/vul2OtherTemplate\")\n" +
- "public String handleTemplateInjection(@RequestParam(\"content\") String content,\n" +
- " @RequestParam(\"type\") String type, Model model) {\n" +
+const vul2OtherTemplate = "// th:utext会把内容作为HTML渲染;th:text会进行HTML转义\n" +
+ "public String handleTemplateInjection(String payload,String type, Model model) {\n" +
" if (\"html\".equals(type)) {\n" +
- " model.addAttribute(\"html\", content);\n" +
+ " model.addAttribute(\"html\", payload);\n" +
" } else if (\"text\".equals(type)) {\n" +
- " model.addAttribute(\"text\", content);\n" +
+ " model.addAttribute(\"text\", payload);\n" +
" }\n" +
" return \"vul/xss/other\";\n" +
"}\n" +
@@ -228,7 +274,7 @@ const vul2OtherTemplate = "@GetMapping(\"/vul2OtherTemplate\")\n" +
"\n" +
"
\n" +
"
\n" +
- "
\n"
+ ""
const vul3SCMSec = "// jQuery依赖\n" +
"\n" +
" \n" +
@@ -247,8 +293,24 @@ const vul3SCMSec = "// jQuery依赖\n" +
"\n" +
"// Ueditor编辑器未做任何限制 抓上传数据包后,可以上传任意类型文件";
+const vulHtml5 = "1、PostMessage XSS\n" +
+ "// 接收端:直接使用innerHTML插入消息\n" +
+ "window.addEventListener('message', function(event) {\n" +
+ " // 故意不验证origin\n" +
+ " document.getElementById('messageContainer').innerHTML = event.data;\n" +
+ "});\n" +
+ "2、WebSocket XSS\n" +
+ "// 客户端:直接使用innerHTML插入消息\n" +
+ "ws.onmessage = function(event) {\n" +
+ " document.getElementById('wsMessageContainer').innerHTML = event.data;\n" +
+ "};\n" +
+ "// 服务端:直接广播用户输入\n" +
+ "protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {\n" +
+ " broadcast(message.getPayload());\n" +
+ "}"
+
const vul1RawJoint = "// 原生sql语句动态拼接 参数未进行任何处理\n" +
- "public R vul1RawJoint(@ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,@ApiParam(name = \"id\", value = \"用户ID\") @RequestParam(required = false) String id,@ApiParam(name = \"username\", value = \"用户名\") @RequestParam(required = false) String username,@ApiParam(name = \"password\", value = \"密码\") @RequestParam(required = false) String password) {\n" +
+ "public R vul1(String type,String id,String username,String password) {\n" +
" //注册数据库驱动类\n" +
" Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" +
"\n" +
@@ -259,58 +321,60 @@ const vul1RawJoint = "// 原生sql语句动态拼接 参数未进行任何处理
" Statement stmt = conn.createStatement();\n" +
" switch (type) {\n" +
" case \"add\":\n" +
- " sql = \"INSERT INTO users (user, pass) VALUES ('\" + username + \"', '\" + password + \"')\"; //这里没有标识id id自增长\n" +
+ " //这里没有标识id id自增长\n" +
+ " sql = \"INSERT INTO sqli (username, password) VALUES ('\" + username + \"', '\" + password + \"')\";\n" +
" //通过Statement对象执行SQL语句,得到ResultSet对象-查询结果集\n" +
- " rowsAffected = stmt.executeUpdate(sql); // 这里注意一下 insert、update、delete 语句应使用executeUpdate()\n" +
+ " // 这里注意一下 insert、update、delete 语句应使用executeUpdate()\n" +
+ " rowsAffected = stmt.executeUpdate(sql);\n" +
" //关闭ResultSet结果集 Statement对象 以及数据库Connection对象 释放资源\n" +
" stmt.close();\n" +
" conn.close();\n" +
" return R.ok(message);\n" +
" case \"delete\":\n" +
- " sql = \"DELETE FROM users WHERE id = '\" + id + \"'\";\n" +
+ " sql = \"DELETE FROM sqli WHERE id = '\" + id + \"'\";\n" +
" rowsAffected = stmt.executeUpdate(sql);\n" +
" ...\n" +
" case \"update\":\n" +
- " sql = \"UPDATE users SET pass = '\" + password + \"', user = '\" + username + \"' WHERE id = '\" + id + \"'\";\n" +
+ " sql = \"UPDATE sqli SET password = '\" + password + \"', username = '\" + username + \"' WHERE id = '\" + id + \"'\";\n" +
" rowsAffected = stmt.executeUpdate(sql);\n" +
" ...\n" +
" case \"select\":\n" +
- " sql = \"SELECT * FROM users WHERE id = \" + id;\n" +
+ " sql = \"SELECT * FROM sqli WHERE id = \" + id;\n" +
" ResultSet rs = stmt.executeQuery(sql);\n" +
" ...\n" +
" }\n" +
"}"
-const vul2prepareStatementJoint = "// 虽然使用了 conn.prepareStatement(sql) 创建了一个 PreparedStatement 对象,但在执行 stmt.executeUpdate(sql) 时,却是传递了完整的 SQL 语句作为参数,而不是使用了预编译的功能\n" +
- "public R vul2prepareStatementJoint(@ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,@ApiParam(name = \"id\", value = \"用户ID\") @RequestParam(required = false) String id,@ApiParam(name = \"username\", value = \"用户名\") @RequestParam(required = false) String username,@ApiParam(name = \"password\", value = \"密码\") @RequestParam(required = false) String password) {\n" +
+const vul2prepareStatementJoint = "// 虽然使用了conn.prepareStatement(sql)创建了一个PreparedStatement对象,但在执行 stmt.executeUpdate(sql)时,却是传递了完整的SQL语句作为参数,而不是使用了预编译的功能\n" +
+ "public R vul2(String type,String id,String username,String password) {\n" +
" Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" +
" Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" +
" PreparedStatement stmt;\n" +
" switch (type) {\n" +
" case \"add\":\n" +
- " sql = \"INSERT INTO users (user, pass) VALUES ('\" + username + \"', '\" + password + \"')\";\n" +
+ " sql = \"INSERT INTO sqli (username, password) VALUES ('\" + username + \"', '\" + password + \"')\";\n" +
" stmt = conn.prepareStatement(sql);\n" +
" rowsAffected = stmt.executeUpdate(sql);\n" +
" ...\n" +
" case \"delete\":\n" +
- " sql = \"DELETE FROM users WHERE id = '\" + id + \"'\";\n" +
+ " sql = \"DELETE FROM sqli WHERE id = '\" + id + \"'\";\n" +
" stmt = conn.prepareStatement(sql);\n" +
" rowsAffected = stmt.executeUpdate(sql);\n" +
" ...\n" +
" case \"update\":\n" +
- " sql = \"UPDATE users set pass = '\" + password + \"' where id = '\" + id + \"'\";\n" +
+ " sql = \"UPDATE sqli SET username = '\" + username + \"', password = '\" + password + \"' WHERE id = '\" + id + \"'\";\n" +
" stmt = conn.prepareStatement(sql);\n" +
" rowsAffected = stmt.executeUpdate(sql);\n" +
" ...\n" +
" case \"select\":\n" +
- " sql = \"SELECT * FROM users WHERE id = \" + id;\n" +
+ " sql = \"SELECT * FROM sqli WHERE id = \" + id;\n" +
" stmt = conn.prepareStatement(sql);\n" +
" ResultSet rs = stmt.executeQuery(sql);\n" +
" ...\n" +
" }\n" +
"}"
const vul3JdbcTemplateJoint = "// JDBCTemplate是Spring对JDBC的封装,底层实现实际上还是JDBC\n" +
- "public R vul3JdbcTemplateJoint(@ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,@ApiParam(name = \"id\", value = \"用户ID\") @RequestParam(required = false) String id,@ApiParam(name = \"username\", value = \"用户名\") @RequestParam(required = false) String username,@ApiParam(name = \"password\", value = \"密码\") @RequestParam(required = false) String password) {\n" +
+ "public R vul3(String type,String id,String username,String password) {\n" +
" DriverManagerDataSource dataSource = new DriverManagerDataSource();\n" +
" dataSource.setDriverClassName(\"com.mysql.cj.jdbc.Driver\");\n" +
" dataSource.setUrl(dbUrl);\n" +
@@ -319,59 +383,63 @@ const vul3JdbcTemplateJoint = "// JDBCTemplate是Spring对JDBC的封装,底层
" JdbcTemplate jdbctemplate = new JdbcTemplate(dataSource);\n" +
" switch (type) {\n" +
" case \"add\":\n" +
- " sql = \"INSERT INTO users (user, pass) VALUES ('\" + username + \"', '\" + password + \"')\";\n" +
- " rowsAffected = jdbctemplate.update(sql); //Spring的JdbcTemplate会自动管理连接的获取和释放,不需要手动关闭连接\n" +
+ " sql = \"INSERT INTO sqli (username, password) VALUES ('\" + username + \"', '\" + password + \"')\";\n" +
+ " //Spring的JdbcTemplate会自动管理连接的获取和释放,不需要手动关闭连接\n" +
+ " rowsAffected = jdbctemplate.update(sql);\n" +
" ...\n" +
" case \"delete\":\n" +
- " sql = \"DELETE FROM users WHERE id = '\" + id + \"'\";\n" +
+ " sql = \"DELETE FROM sqli WHERE id = '\" + id + \"'\";\n" +
" rowsAffected = jdbctemplate.update(sql);\n" +
" ...\n" +
" case \"update\":\n" +
- " sql = \"UPDATE users set pass = '\" + password + \"' where id = '\" + id + \"'\";\n" +
+ " sql = \"UPDATE sqli SET username = '\" + username + \"', password = '\" + password + \"' WHERE id = '\" + id + \"'\";\n" +
" rowsAffected = jdbctemplate.update(sql);\n" +
" ...\n" +
" case \"select\":\n" +
- " sql = \"SELECT * FROM users WHERE id = \" + id;\n" +
- " stringObjectMap = jdbctemplate.queryForMap(sql);\n" +
+ " sql = \"SELECT * FROM sqli WHERE id = \" + id;\n" +
+ " resultList = jdbctemplate.queryForList(sql);\n" +
" ...\n" +
" }\n" +
"}"
const safe1PrepareStatementParametric = "// 采用预编译的方法,使用?占位,也叫参数化的SQL\n" +
- "public R safe1PrepareStatementParametric(@ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,@ApiParam(name = \"id\", value = \"用户ID\") @RequestParam(required = false) String id,@ApiParam(name = \"username\", value = \"用户名\") @RequestParam(required = false) String username,@ApiParam(name = \"password\", value = \"密码\") @RequestParam(required = false) String password) {\n" +
+ "public R safe1(String type,String id,String username,String password) {\n" +
" Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" +
" Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" +
" PreparedStatement stmt;\n" +
" switch (type) {\n" +
" case \"add\":\n" +
- " sql = \"INSERT INTO users (user, pass) VALUES (?, ?)\"; // 这里可以看到使用了?占位符 sql语句和参数进行分离\n" +
+ " // 这里可以看到使用了?占位符 sql语句和参数进行分离\n" +
+ " sql = \"INSERT INTO sqli (username, password) VALUES (?, ?)\"; \n" +
" stmt = conn.prepareStatement(sql);\n" +
- " stmt.setString(ueditor, username); // 参数化处理\n" +
+ " // 参数化处理\n" +
+ " stmt.setString(1, username); \n" +
" stmt.setString(2, password);\n" +
- " rowsAffected = stmt.executeUpdate(); // 使用预编译时 不需要传递sql语句\n" +
- "\n" +
+ " // 使用预编译时 不需要传递sql语句\n" +
+ " rowsAffected = stmt.executeUpdate();\n" +
" case \"delete\":\n" +
- " sql = \"DELETE FROM users WHERE id = ?\";\n" +
+ " sql = \"DELETE FROM sqli WHERE id = ?\";\n" +
" stmt = conn.prepareStatement(sql);\n" +
- " stmt.setString(ueditor, id);\n" +
+ " stmt.setString(1, id);\n" +
" rowsAffected = stmt.executeUpdate();\n" +
" ...\n" +
" case \"update\":\n" +
- " sql = \"UPDATE users set pass = ? where id = ?\";\n" +
+ " sql = \"UPDATE sqli SET username = ?, password = ? WHERE id = ?\";\n" +
" stmt = conn.prepareStatement(sql);\n" +
- " stmt.setString(ueditor, password);\n" +
- " stmt.setString(2, id);\n" +
+ " stmt.setString(1, username); \n" +
+ " stmt.setString(2, password);\n" +
+ " stmt.setString(3, id);\n" +
" rowsAffected = stmt.executeUpdate();\n" +
" ...\n" +
" case \"select\":\n" +
- " sql = \"SELECT * FROM users WHERE id = ?\";\n" +
+ " sql = \"SELECT * FROM sqli WHERE id = ?\";\n" +
" stmt = conn.prepareStatement(sql);\n" +
- " stmt.setString(ueditor, id);\n" +
+ " stmt.setString(1, id);\n" +
" ResultSet rs = stmt.executeQuery();\n" +
" ...\n" +
" }\n" +
"}"
const safe2JdbcTemplatePrepareStatementParametric = "// JDBCTemplate预编译 此时在常规DML场景有效的防止了SQL注入攻击的发生\n" +
- "public R safe2JdbcTemplatePrepareStatementParametric(@ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,@ApiParam(name = \"id\", value = \"用户ID\") @RequestParam(required = false) String id,@ApiParam(name = \"username\", value = \"用户名\") @RequestParam(required = false) String username,@ApiParam(name = \"password\", value = \"密码\") @RequestParam(required = false) String password) {\n" +
+ "public R safe2(String type,String id,String username,String password) {\n" +
" DriverManagerDataSource dataSource = new DriverManagerDataSource();\n" +
" dataSource.setDriverClassName(\"com.mysql.cj.jdbc.Driver\");\n" +
" dataSource.setUrl(dbUrl);\n" +
@@ -380,26 +448,26 @@ const safe2JdbcTemplatePrepareStatementParametric = "// JDBCTemplate预编译
" JdbcTemplate jdbctemplate = new JdbcTemplate(dataSource);\n" +
" switch (type) {\n" +
" case \"add\":\n" +
- " sql = \"INSERT INTO users (user, pass) VALUES (?,?)\";\n" +
+ " sql = \"INSERT INTO sqli (username, password) VALUES (?,?)\";\n" +
" rowsAffected = jdbctemplate.update(sql, username, password);\n" +
" ...\n" +
" case \"delete\":\n" +
- " sql = \"DELETE FROM users WHERE id = ?\";\n" +
+ " sql = \"DELETE FROM sqli WHERE id = ?\";\n" +
" rowsAffected = jdbctemplate.update(sql, id);\n" +
" ...\n" +
" case \"update\":\n" +
- " sql = \"UPDATE users set pass = ? where id = ?\";\n" +
- " rowsAffected = jdbctemplate.update(sql, username, id);\n" +
+ " sql = \"UPDATE sqli SET username = ?, password = ? WHERE id = ?\";\n" +
+ " rowsAffected = jdbctemplate.update(sql, username, password, id);\n" +
" ...\n" +
" case \"select\":\n" +
- " sql = \"SELECT * FROM users WHERE id = ?\";\n" +
+ " sql = \"SELECT * FROM sqli WHERE id = ?\";\n" +
" stringObjectMap = jdbctemplate.queryForMap(sql, id);\n" +
" ...\n" +
" }\n" +
- "}"
-const safe3BlacklistcheckSqlBlackList = "// 检测用户输入是否存在敏感字符:'、;、--、+、,、%、=、>、<、*、(、)、and、or、exeinsert、select、delete、update、count、drop、chr、midmaster、truncate、char、declare\n" +
- "public R safe3BlacklistcheckSqlBlackList(@ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,\n" +
- " @ApiParam(name = \"id\", value = \"用户ID\") @RequestParam(required = false) String id,@ApiParam(name = \"username\", value = \"用户名\") @RequestParam(required = false) String username,@ApiParam(name = \"password\", value = \"密码\") @RequestParam(required = false) String password) {\n" +
+ "}\n"
+const safe3BlacklistcheckSqlBlackList = "// 黑名单只能作为辅助检测或拦截,不应替代参数化查询。\n" +
+ "// 遗漏关键字、编码绕过、语法变形都可能导致绕过。\n" +
+ "public R safe3(String type,String id,String username,String password) {\n" +
" Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" +
" Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" +
" Statement stmt = conn.createStatement();\n" +
@@ -408,84 +476,80 @@ const safe3BlacklistcheckSqlBlackList = "// 检测用户输入是否存在敏感
" if (checkUserInput.checkSqlBlackList(username) || checkUserInput.checkSqlBlackList(password)) {\n" +
" return R.error(\"黑名单检测到非法SQL注入!\");\n" +
" } else {\n" +
- " sql = \"INSERT INTO users (user, pass) VALUES ('\" + username + \"', '\" + password + \"')\";\n" +
+ " sql = \"INSERT INTO sqli (username, password) VALUES ('\" + username + \"', '\" + password + \"')\";\n" +
" rowsAffected = stmt.executeUpdate(sql);\n" +
" ...\n" +
" case \"delete\":\n" +
" if (checkUserInput.checkSqlBlackList(id)) {\n" +
" return R.error(\"黑名单检测到非法SQL注入!\");\n" +
" } else {\n" +
- " sql = \"DELETE FROM users WHERE id = '\" + id + \"'\";\n" +
+ " sql = \"DELETE FROM sqli WHERE id = '\" + id + \"'\";\n" +
" rowsAffected = stmt.executeUpdate(sql);\n" +
" ...\n" +
" case \"update\":\n" +
- " if (checkUserInput.checkSqlBlackList(id) || checkUserInput.checkSqlBlackList(username) || checkUserInput.checkSqlBlackList(id)) {\n" +
+ " if (checkUserInput.checkSqlBlackList(id) || checkUserInput.checkSqlBlackList(username) || checkUserInput.checkSqlBlackList(password)) {\n" +
" return R.error(\"黑名单检测到非法SQL注入!\");\n" +
" } else {\n" +
- " sql = \"UPDATE users SET pass = '\" + password + \"', user = '\" + username + \"' WHERE id = '\" + id + \"'\";\n" +
- " log.info(\"当前执行数据更新操作:\" + sql);\n" +
+ " sql = \"UPDATE sqli SET password = '\" + password + \"', username = '\" + username + \"' WHERE id = '\" + id + \"'\";\n" +
" rowsAffected = stmt.executeUpdate(sql);\n" +
" ...\n" +
" case \"select\":\n" +
" if (checkUserInput.checkSqlBlackList(id)) {\n" +
" return R.error(\"黑名单检测到非法SQL注入!\");\n" +
" } else {\n" +
- " sql = \"SELECT * FROM users WHERE id = \" + id;\n" +
+ " sql = \"SELECT * FROM sqli WHERE id = \" + id;\n" +
" ResultSet rs = stmt.executeQuery(sql);\n" +
" ...\n" +
" }\n" +
- "}"
+ "}\n"
const safe4RequestRarameterValidate = "// 强制类型转换 对用户请求参数进行校验\n" +
- "public R safe4RequestRarameterValidate(@ApiParam(name = \"id\", value = \"用户ID\") @RequestParam(required = false) Integer id) {\n" +
+ "public R safe4(Integer id) {\n" +
" Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" +
" Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" +
" Statement stmt = conn.createStatement();\n" +
" message = checkUserInput.checkUser(id);\n" +
" if (!message.isEmpty()) return R.error(message);\n" +
- " sql = \"SELECT * FROM users WHERE id = \" + id;\n" +
- " log.info(\"当前执行数据查询操作:\" + sql);\n" +
+ " sql = \"SELECT * FROM sqli WHERE id = \" + id;\n" +
" ResultSet rs = stmt.executeQuery(sql);\n" +
" ...\n" +
"}"
-const safe4EASAPIFilter = "// ESAPI提供了多种输入验证API,提供对XSS攻击和SQL注入攻击等的防护\n" +
- "public R safe4EASAPIFilter(@ApiParam(name = \"id\", value = \"用户ID\") @RequestParam(required = false) String id) {\n" +
+const safe4EASAPIFilter = "// encodeForSQL是历史方案或特定数据库Codec场景下的补充手段,不推荐作为首选修复。\n" +
+ "// SQL注入首选修复仍然是参数化查询。\n" +
+ "public R safe5(String id) {\n" +
" Codec oracleCodec = new OracleCodec();\n" +
" Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" +
" Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" +
"\n" +
" Statement stmt = conn.createStatement();\n" +
- " // 使用了 Oracle 的编解码器 OracleCodec 和 ESAPI 库来对 ID 进行编码,以防止 SQL 注入攻击。\n" +
+ " // 使用OracleCodec对ID进行SQL编码,仅作为特定场景补充。\n" +
" String sql = \"select * from sqli where id = '\" + ESAPI.encoder().encodeForSQL(oracleCodec, id) + \"'\";\n" +
- "\t// String sql = \"select * from sqli where id = '\" + id + \"'\";\n" +
- " String sql = \"select * from users where id = '\" + id + \"'\";\n" +
- " log.info(\"当前执行数据查询操作:\" + sql);\n" +
+ " // String sql = \"select * from sqli where id = '\" + id + \"'\";\n" +
" ResultSet rs = stmt.executeQuery(sql);\n" +
- " \n" +
"}"
-const special1OrderBy = "// ORDER BY关键字用于按升序或降序对结果集进行排序。 由于order by后面需要紧跟column_name,而预编译是参数化字符串,而order by后面紧跟字符串就会不支持原有功能 使用默认排序,因此通常防御order by注入需要使用白名单的方式\n" +
- "public R special1OrderBy(@ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,@ApiParam(name = \"field\", value = \"字段名\") @RequestParam(required = false) String field) {\n" +
+const special1OrderBy = "// 占位符只能绑定“值”,不能绑定列名、表名、关键字、排序方向等SQL结构。\n" +
+ "// ORDER BY动态字段应使用枚举映射或白名单。\n" +
+ "public R special1OrderBy(String type,String field) {\n" +
" Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" +
" Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" +
" PreparedStatement preparedStatement;\n" +
" switch (type) {\n" +
" case \"raw\":\n" +
- " sql = \"SELECT * FROM users ORDER BY \" + field;\n" +
+ " sql = \"SELECT * FROM sqli ORDER BY \" + field;\n" +
" preparedStatement = conn.prepareStatement(sql);\n" +
" rs = preparedStatement.executeQuery();\n" +
" ...\n" +
" case \"prepareStatement\":\n" +
- " // 可以测试下 预编译没有报错 不过插入语句不生效 默认使用主键升序\n" +
- " sql = \"select * from users order by ?\";\n" +
+ " // ORDER BY ? 会把字段名当作普通值或表达式处理,不会按传入字段排序。\n" +
+ " sql = \"select * from sqli order by ?\";\n" +
" preparedStatement = conn.prepareStatement(sql);\n" +
- " preparedStatement.setString(ueditor, field);\n" +
+ " preparedStatement.setString(1, field);\n" +
" rs = preparedStatement.executeQuery();\n" +
" ...\n" +
" case \"writeList\":\n" +
- " sql = \"SELECT * FROM users ORDER BY \" + field;\n" +
- " if (checkUserInput.chechSqlWhiteList(field)) {\n" +
+ " if (!checkUserInput.checkSqlWhiteList(field)) {\n" +
" return R.error(\"field字段不合法!\");\n" +
" }\n" +
- " log.info(\"当前执行数据排序操作:\" + sql + \" 参数:\" + field);\n" +
+ " sql = \"SELECT * FROM sqli ORDER BY \" + field;\n" +
" preparedStatement = conn.prepareStatement(sql);\n" +
" rs = preparedStatement.executeQuery();\n" +
" }\n" +
@@ -496,15 +560,13 @@ const special1OrderBy = "// ORDER BY关键字用于按升序或降序对结果
"public boolean checkSqlWhiteList(String content) {\n" +
" String[] white_list = {\"id\", \"username\", \"password\"};\n" +
" for (String s : white_list) {\n" +
- " if (content.toLowerCase().contains(s)) {\n" +
+ " if (content.toLowerCase().equals(s)) {\n" +
" return true;\n" +
" }\n" +
" }\n" +
" return false;\n" +
"}"
-const special2Like = "@GetMapping(\"/special2-Like\")\n" +
- "public R special2Like(@ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,@ApiParam(name = \"keyword\", value = \"关键词\") @RequestParam(required = false) String keyword\n" +
- ") {\n" +
+const special2Like = "public R special2Like(String type,String keyword) {\n" +
" Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" +
" Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" +
" ...\n" +
@@ -522,32 +584,71 @@ const special2Like = "@GetMapping(\"/special2-Like\")\n" +
" ...\n" +
" }\n" +
"}"
-const special3Limit = "@GetMapping(\"/special3-Limit\")\n" +
- "public R special3Limit(@ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,@ApiParam(name = \"size\", value = \"数量\") @RequestParam(required = false) String size\n" +
- ") {\n" +
- " Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" +
- " Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" +
- " ...\n" +
- " switch (type) {\n" +
- " case \"raw\":\n" +
- " sql = \"SELECT * FROM sqli ORDER BY id DESC LIMIT \" + size;\n" +
- " log.info(\"当前执行数据查询操作:\" + sql);\n" +
- " rs = stmt.executeQuery(sql);\n" +
- " ...\n" +
- " case \"prepareStatement\": // 使用预编译\n" +
- " sql = \"SELECT * FROM sqli ORDER BY id DESC LIMIT ?\";\n" +
- " preparedStatement = conn.prepareStatement(sql);\n" +
- " preparedStatement.setString(1, size);\n" +
- " rs = preparedStatement.executeQuery();\n" +
- " ...\n" +
+const special3Limit = "public R special3Limit(String type,String size) {\n" +
+ " Class.forName(\"com.mysql.cj.jdbc.Driver\");\n" +
+ " Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPass);\n" +
+ " ...\n" +
+ " switch (type) {\n" +
+ " case \"raw\":\n" +
+ " sql = \"SELECT * FROM sqli ORDER BY id DESC LIMIT \" + size;\n" +
+ " rs = stmt.executeQuery(sql);\n" +
+ " ...\n" +
+ " // 使用预编译\n" +
+ " case \"prepareStatement\":\n" +
+ " sql = \"SELECT * FROM sqli ORDER BY id DESC LIMIT ?\";\n" +
+ " preparedStatement = conn.prepareStatement(sql);\n" +
+ " preparedStatement.setInt(1, Integer.parseInt(size));\n" +
+ " rs = preparedStatement.executeQuery();\n" +
+ " ...\n" +
" }\n" +
"}"
+const special4SecondOrder = "// 第一步:参数化写入恶意数据,此时不触发SQL注入。\n" +
+ "public R special4SecondOrder(String type,String id,String username,String password) {\n" +
+ " switch (type) {\n" +
+ " case \"store\":\n" +
+ " String insertSql = \"INSERT INTO sqli (username, password) VALUES (?, ?)\";\n" +
+ " PreparedStatement ps = conn.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS);\n" +
+ " ps.setString(1, username);\n" +
+ " ps.setString(2, password);\n" +
+ " ps.executeUpdate();\n" +
+ " ...\n" +
+ " case \"trigger\":\n" +
+ " // 第二步:先通过ID取出已存储的username。\n" +
+ " String storedUsername = queryUsernameById(id);\n" +
+ " // 漏洞点:数据库中的历史数据再次被拼接进SQL结构。\n" +
+ " String vulSql = \"SELECT id, username, password FROM sqli WHERE username = '\" + storedUsername + \"'\";\n" +
+ " ResultSet rs = stmt.executeQuery(vulSql);\n" +
+ " ...\n" +
+ " case \"safeTrigger\":\n" +
+ " String safeSql = \"SELECT id, username, password FROM sqli WHERE username = ?\";\n" +
+ " PreparedStatement safePs = conn.prepareStatement(safeSql);\n" +
+ " safePs.setString(1, storedUsername);\n" +
+ " ResultSet safeRs = safePs.executeQuery();\n" +
+ " ...\n" +
+ " }\n" +
+ "}\n"
+const special5Union = "// UNION回显要求原查询与联合查询列数一致、类型兼容。\n" +
+ "public R special5Union(String type,String id) {\n" +
+ " switch (type) {\n" +
+ " case \"raw\":\n" +
+ " String sql = \"SELECT id, username, password FROM sqli WHERE id = \" + id;\n" +
+ " // 示例:id = -1 UNION SELECT 1,database(),user()\n" +
+ " ResultSet rs = stmt.executeQuery(sql);\n" +
+ " ...\n" +
+ " case \"prepareStatement\":\n" +
+ " String safeSql = \"SELECT id, username, password FROM sqli WHERE id = ?\";\n" +
+ " PreparedStatement ps = conn.prepareStatement(safeSql);\n" +
+ " ps.setString(1, id);\n" +
+ " ResultSet safeRs = ps.executeQuery();\n" +
+ " ...\n" +
+ " }\n" +
+ "}\n"
// MyBatis
const vul1CustomMethod = "vul1CustomMethod"
const safe1NativeMethod = "// 这里以增加功能为例\n" +
"// Controller层\n" +
- "public R safe1NativeMethod(\n" +
+ "public R safe1(\n" +
"switch (type) {\n" +
" case \"add\":\n" +
" rowsAffected = sqliService.nativeInsert(new Sqli(id, username, password));\n" +
@@ -566,6 +667,7 @@ const safe1NativeMethod = "// 这里以增加功能为例\n" +
const safe2CustomMethod = "// 这里以增加功能为例\n" +
"// Controller层\n" +
+ "public R safe2( \n" +
"switch (type) {\n" +
" case \"add\":\n" +
" //这里插入数据使用MyBatiX插件生成的方法\n" +
@@ -584,7 +686,7 @@ const safe2CustomMethod = "// 这里以增加功能为例\n" +
"// Mapper层\n" +
"\n" +
" insert into sqli (id,username,password) values (#{id,jdbcType=INTEGER},#{username,jdbcType=VARCHAR},#{password,jdbcType=VARCHAR})\n" +
- " \n"
+ ""
const mybatisSpecial1OrderBy =
"// Controller层\n" +
@@ -598,6 +700,9 @@ const mybatisSpecial1OrderBy =
" sqlis = sqliService.orderByPrepareStatement(field);\n" +
" break;\n" +
" case \"writeList\":\n" +
+ " if (!checkUserInput.checkSqlWhiteList(field)) {\n" +
+ " return R.error(\"field字段不合法!\");\n" +
+ " }\n" +
" sqlis = sqliService.orderByWriteList(field);\n" +
" ...\n" +
"// Service层\n" +
@@ -615,21 +720,21 @@ const mybatisSpecial1OrderBy =
" return sqliMapper.orderByWriteList(field);\n" +
"}\n" +
"// Mapper层\n" +
- "\n" +
+ "\n" +
"\n" +
" SELECT * FROM sqli\n" +
" \n" +
" ORDER BY ${field}\n" +
" \n" +
" \n" +
- "\n" +
+ "\n" +
"\n" +
" SELECT * FROM sqli\n" +
" \n" +
" ORDER BY #{field}\n" +
" \n" +
" \n" +
- "\n" +
+ "\n" +
"\n" +
" SELECT * FROM sqli\n" +
" \n" +
@@ -646,14 +751,10 @@ const mybatisSpecial1OrderBy =
" \n" +
" "
-const mybatisSpecial2Like =
- "// Controller层\n" +
- "@PostMapping(\"/special2-Like\")\n" +
+const mybatisSpecial2Like = "// Controller层\n" +
"public R special1OrderBy() {\n" +
"@PostMapping(\"/special2-Like\")\n" +
- "public R special2Like(\n" +
- " @ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,@ApiParam(name = \"keyword\", value = \"关键词\") @RequestParam(required = false) String keyword\n" +
- ") {\n" +
+ "public R special2Like(String type,String keyword) {\n" +
" List sqlis = new ArrayList<>();\n" +
" switch (type) {\n" +
" case \"raw\":\n" +
@@ -681,11 +782,8 @@ const mybatisSpecial2Like =
" SELECT * FROM sqli WHERE username LIKE CONCAT('%', #{keyword}, '%')\n" +
""
-const mybatisSpecial3In =
- "// Controller层\n" +
- "@PostMapping(\"/special3-In\")\n" +
- "public R special3In(\n" +
- " @ApiParam(name = \"type\", value = \"操作类型\", required = true) @RequestParam String type,@ApiParam(name = \"scope\", value = \"关键词\") @RequestParam(required = false) String scope) {\n" +
+const mybatisSpecial3In = "// Controller层\n" +
+ "public R special3In(String type,String scope) {\n" +
" switch (type) {\n" +
" case \"raw\":\n" +
" sqlis = sqliService.inVul(scope);\n" +
@@ -694,8 +792,11 @@ const mybatisSpecial3In =
" sqlis = sqliService.inPrepareStatement(scope);\n" +
" break;\n" +
" case \"Foreach\":\n" +
- "\n" +
- " sqlis = sqliService.inSafeForeach(parseInputToList(scope));\n" +
+ " List idList = parseInputToList(scope);\n" +
+ " if (idList.isEmpty()) {\n" +
+ " return R.error(\"scope中没有合法整数ID!\");\n" +
+ " }\n" +
+ " sqlis = sqliService.inSafeForeach(idList);\n" +
" break;\n" +
" ...\n" +
"// Service层\n" +
@@ -728,36 +829,176 @@ const mybatisSpecial3In =
const vulHibernate = "vulHibernate"
+const safeHibernate = "safeHibernate"
+
+const vulJPA = "vulJPA"
+const safeJPA = "safeJPA"
+
+
// 任意文件类-文件上传
-const anyFileUploadCode = "// 原生漏洞环境,未做任何限制\n" +
- "@RequestMapping(\"/anyFIleUpload\")\n" +
- "public R vul1AnyFIleUpload(@RequestParam(\"file\") MultipartFile file, HttpServletRequest request) {\n" +
+const anyFileUploadCode = "// 原生漏洞场景,未做任何限制\n" +
+ "public R vul(MultipartFile file, HttpServletRequest request) {\n" +
" String res;\n" +
- " String suffix = FilenameUtils.getExtension(file.getOriginalFilename()); // 查找文件名中最后一个点(.)之后的字符串\n" +
+ " String suffix = FilenameUtils.getExtension(\n" +
+ " // 查找文件名中最后一个点(.)之后的字符串\n" +
+ " file.getOriginalFilename()); \n" +
" String path = request.getScheme() + \"://\" + request.getServerName() + \":\" + request.getServerPort() + \"/file/\";\n" +
" res = uploadUtil.uploadFile(file, suffix, path);\n" +
" return R.ok(res);\n" +
"}\n" +
- "// uploadFile方法详见文件上传导致XSS模块"
+ "// uploadFile方法详见文件上传导致XSS模块\n"
const anyFileUploadWhiteCode = "// 检测文件后缀,做白名单过滤\n" +
+ "String suffix = FilenameUtils.getExtension(file.getOriginalFilename());\n" +
"if (!checkUserInput.checkFileSuffixWhiteList(suffix)){\n" +
" return R.error(\"只能上传图片哦!\");\n" +
"}\n" +
+ "if (!isAllowedImageContent(file, suffix)) {\n" +
+ " return R.error(\"文件内容与图片类型不匹配!\");\n" +
+ "}\n" +
"\n" +
"public boolean checkFileSuffixWhiteList(String suffix) {\n" +
+ " if (suffix == null || suffix.isEmpty()) {\n" +
+ " return false;\n" +
+ " }\n" +
" String[] white_list = {\"jpg\", \"png\", \"gif\",\"jpeg\",\"bmp\",\"ico\"};\n" +
" for (String s : white_list) {\n" +
- " if (suffix.toLowerCase().contains(s)) {\n" +
+ " if (suffix.equalsIgnoreCase(s)) {\n" +
" return true;\n" +
" }\n" +
" }\n" +
" return false;\n" +
+ "}\n" +
+ "\n" +
+ "private boolean isAllowedImageContent(MultipartFile file, String suffix) throws IOException {\n" +
+ " if (\"ico\".equalsIgnoreCase(suffix)) {\n" +
+ " // 校验 ICO 文件头:00 00 01 00\n" +
+ " }\n" +
+ " BufferedImage image = ImageIO.read(file.getInputStream());\n" +
+ " return image != null;\n" +
+ " // ImageIO 解析异常时返回 false,避免损坏图片导致 500\n" +
+ "}"
+
+const vul1Native = "public R vul1(@RequestParam String username) {\n" +
+ " try {\n" +
+ " String sql = \"SELECT * FROM sqli WHERE username = '\" + username + \"'\";\n" +
+ " Object[] result = (Object[]) hibernateTemplate.execute(session ->\n" +
+ " session.createNativeQuery(sql).uniqueResult()\n" +
+ " );\n" +
+ " message = \"查询成功,用户名:\" + result[1] + \" 密码:\" +result[2];\n" +
+ " return R.ok(message);\n" +
+ " } catch (Exception e) {\n" +
+ " log.error(\"查询失败\", e);\n" +
+ " return R.error(e.getMessage());\n" +
+ " }\n" +
+ "}"
+const vul2Hql = "public R vul2(@RequestParam String username) {\n" +
+ " try {\n" +
+ " String hql = \"FROM Sqli WHERE username = '\" + username + \"'\";\n" +
+ " Sqli result = (Sqli) hibernateTemplate.execute(session ->\n" +
+ " session.createQuery(hql).uniqueResult()\n" +
+ " );\n" +
+ " message = \"查询成功,用户名:\" +result.getUsername()+ \" 密码:\" +result.getPassword();\n" +
+ " return R.ok(message);\n" +
+ " } catch (Exception e) {\n" +
+ " log.error(\"查询失败\", e);\n" +
+ " return R.error(e.getMessage());\n" +
+ " }\n" +
+ "}"
+const safe1Param = "public R safe(@RequestParam String username) {\n" +
+ " try {\n" +
+ " String hql = \"FROM Sqli WHERE username = :username\";\n" +
+ " Sqli result = hibernateTemplate.execute(session ->\n" +
+ " (Sqli) session.createQuery(hql)\n" +
+ " .setParameter(\"username\", username)\n" +
+ " .uniqueResult()\n" +
+ " );\n" +
+ " message = \"查询成功,用户名:\" +result.getUsername()+ \" 密码:\" +result.getPassword();\n" +
+ " return R.ok(message);\n" +
+ " } catch (Exception e) {\n" +
+ " log.error(\"查询失败\", e);\n" +
+ " return R.error(e.getMessage());\n" +
+ " }\n" +
+ "}"
+
+const vul1JpaJpql = "public R vul1(@RequestParam String username) {\n" +
+ " try {\n" +
+ " String jpql = \"SELECT s FROM Sqli s WHERE s.username = '\" + username + \"'\";\n" +
+ " Query query = entityManager.createQuery(jpql);\n" +
+ " List results = query.getResultList();\n" +
+ " if (results == null || results.isEmpty()) {\n" +
+ " return R.error(\"未找到记录\");\n" +
+ " }\n" +
+ " StringBuilder sb = new StringBuilder();\n" +
+ " sb.append(\"查询成功,找到 \").append(results.size()).append(\" 条记录\\n\");\n" +
+ " message = sb.toString();\n" +
+ " log.info(message);\n" +
+ " return R.ok(message);\n" +
+ " } catch (Exception e) {\n" +
+ " String errorMsg = e.getMessage();\n" +
+ " log.error(\"查询失败: {}\", errorMsg, e);\n" +
+ " return R.error(errorMsg);\n" +
+ " }\n" +
+ "}"
+const vul2JpaSort = "public R vul2(@RequestParam String orderBy) {\n" +
+ " try {\n" +
+ " String jpql = \"SELECT s FROM Sqli s ORDER BY s.\" + orderBy;\n" +
+ " Query query = entityManager.createQuery(jpql);\n" +
+ " List results = query.getResultList();\n" +
+ " return R.ok(formatResults(results));\n" +
+ " } catch (Exception e) {\n" +
+ " String errorMsg = e.getMessage();\n" +
+ " log.error(\"查询失败: {}\", errorMsg, e);\n" +
+ " return R.error(errorMsg);\n" +
+ " }\n" +
+ "}"
+const safeJpaParam = "public R safe(@RequestParam String username) {\n" +
+ " try {\n" +
+ " String jpql = \"SELECT s FROM Sqli s WHERE s.username = :username\";\n" +
+ " Query query = entityManager.createQuery(jpql)\n" +
+ " .setParameter(\"username\", username);\n" +
+ " List results = query.getResultList();\n" +
+ " if (results == null || results.isEmpty()) {\n" +
+ " return R.error(\"未找到记录\");\n" +
+ " }\n" +
+ " StringBuilder sb = new StringBuilder();\n" +
+ " sb.append(\"查询成功,找到 \").append(results.size()).append(\" 条记录\\n\");\n" +
+ " message = sb.toString();\n" +
+ " log.info(message);\n" +
+ " return R.ok(message);\n" +
+ " } catch (Exception e) {\n" +
+ " String errorMsg = e.getMessage();\n" +
+ " log.error(\"查询失败: {}\", errorMsg, e);\n" +
+ " return R.error(errorMsg);\n" +
+ " }\n" +
+ "}"
+const safeJpaSort = "public R safeOrder(@RequestParam String orderBy) {\n" +
+ " try {\n" +
+ " Map orderByMap = new HashMap<>();\n" +
+ " orderByMap.put(\"id\", \"id\");\n" +
+ " orderByMap.put(\"username\", \"username\");\n" +
+ " orderByMap.put(\"password\", \"password\");\n" +
+ "\n" +
+ " String safeOrderBy = orderByMap.get(orderBy);\n" +
+ " if (safeOrderBy == null) {\n" +
+ " return R.error(\"排序字段不合法\");\n" +
+ " }\n" +
+ "\n" +
+ " CriteriaBuilder cb = entityManager.getCriteriaBuilder();\n" +
+ " CriteriaQuery cq = cb.createQuery(Sqli.class);\n" +
+ " Root root = cq.from(Sqli.class);\n" +
+ " cq.select(root).orderBy(cb.asc(root.get(safeOrderBy)));\n" +
+ "\n" +
+ " List results = entityManager.createQuery(cq).getResultList();\n" +
+ " return R.ok(formatResults(results));\n" +
+ " } catch (Exception e) {\n" +
+ " String errorMsg = e.getMessage();\n" +
+ " log.error(\"查询失败: {}\", errorMsg, e);\n" +
+ " return R.error(errorMsg);\n" +
+ " }\n" +
"}"
// 任意文件类型-文件删除
-const deleteFile = "@ApiOperation(value = \"漏洞环境:任意文件删除\", notes = \"原生漏洞环境,未做任何限制\")\n" +
- "@RequestMapping(\"/deleteFile\")\n" +
- "public String vulArbitraryFileDeletion(@RequestParam(\"filePath\") String filePath) {\n" +
+const deleteFile = "public String vul(String filePath) {\n" +
" String currentPath = System.getProperty(\"user.dir\");\n" +
" File file = new File(filePath);\n" +
" boolean deleted = false;\n" +
@@ -770,14 +1011,20 @@ const deleteFile = "@ApiOperation(value = \"漏洞环境:任意文件删除\",
" return \"当前路径:\"+currentPath+\" 文件删除失败或文件不存在: \" + filePath;\n" +
" }\n" +
"}"
-const safeDeleteFile = "@ApiOperation(value = \"安全环境:限制文件删除\", notes = \"仅允许删除特定目录中的文件\")\n" +
- "@RequestMapping(\"/safeDeleteFile\")\n" +
- "public String safeFileDelete(@RequestParam(\"fileName\") String fileName) {\n" +
- " String baseDir = sysConstant.getUploadFolder(); // 限制删除文件所在目录为 /static/upload/下\n" +
- " File file = new File(baseDir, fileName);\n" +
+const safeDeleteFile = "public String safe(String fileName) {\n" +
+ " String baseDir = sysConstant.getUploadFolder();\n" +
+ " Path basePath = Paths.get(baseDir).toRealPath();\n" +
+ " Path filePath = basePath.resolve(fileName).normalize();\n" +
+ " if (!filePath.startsWith(basePath)) {\n" +
+ " return \"访问被拒绝:文件路径不合法\";\n" +
+ " }\n" +
" boolean deleted = false;\n" +
- " if (file.exists() && file.getCanonicalPath().startsWith(new File(baseDir).getCanonicalPath())) {\n" +
- " deleted = file.delete();\n" +
+ " if (Files.isRegularFile(filePath)) {\n" +
+ " Path realFilePath = filePath.toRealPath();\n" +
+ " if (!realFilePath.startsWith(basePath)) {\n" +
+ " return \"访问被拒绝:文件真实路径不合法\";\n" +
+ " }\n" +
+ " deleted = Files.deleteIfExists(filePath);\n" +
" }\n" +
" if (deleted) {\n" +
" return \"文件删除成功: \" + fileName;\n" +
@@ -787,16 +1034,14 @@ const safeDeleteFile = "@ApiOperation(value = \"安全环境:限制文件删
"}"
// 任意文件类型-文件读取
-const readFile = "@RequestMapping(\"/readFile\")\n" +
- "@ResponseBody\n" +
- "public String readFile(@RequestParam(\"fileName\") String fileName) throws IOException {\n" +
+const readFile = "public String vul(String fileName) throws IOException {\n" +
" String currentPath = System.getProperty(\"user.dir\");\n" +
" log.info(currentPath);\n" +
" File file = new File(fileName);\n" +
" if (file.exists() && file.isFile()) {\n" +
" Path filePath = file.toPath();\n" +
" // 使用 BufferedReader 和流 API 逐行读取文件\n" +
- " try (var lines = Files.lines(filePath)) {\n" +
+ " try (Stream lines = Files.lines(filePath)) {\n" +
" return lines\n" +
" .map(line -> line + \" \")\n" +
" .collect(Collectors.joining());\n" +
@@ -804,28 +1049,27 @@ const readFile = "@RequestMapping(\"/readFile\")\n" +
" } else {\n" +
" return \"当前路径:\"+currentPath+\" 文件不存在或路径不正确:\" + fileName;\n" +
" }"
-const safeReadFile = "@ApiOperation(value = \"安全读取文件内容\", notes = \"仅允许读取特定目录中的文件内容\")\n" +
- "@RequestMapping(\"/safeReadFile\")\n" +
- "@ResponseBody\n" +
- "public String safeReadFile(@RequestParam(\"fileName\") String fileName) throws IOException {\n" +
- " String baseDir = sysConstant.getUploadFolder(); // 限制删除文件所在目录为 /static/upload/下\n" +
- " Path filePath = Paths.get(baseDir, fileName).normalize(); // 规范化路径\n" +
- " // 确保文件路径在允许的目录中\n" +
- " if (!filePath.startsWith(Paths.get(baseDir))) {\n" +
+const safeReadFile = "public String safe(String fileName) throws IOException {\n" +
+ " String baseDir = sysConstant.getUploadFolder();\n" +
+ " Path basePath = Paths.get(baseDir).toRealPath();\n" +
+ " Path filePath = basePath.resolve(fileName).normalize();\n" +
+ " // 先标准化路径,再确认目标文件仍位于允许目录内\n" +
+ " if (!filePath.startsWith(basePath)) {\n" +
" return \"访问被拒绝:文件路径不合法\";\n" +
" }\n" +
- " File file = filePath.toFile();\n" +
- " if (file.exists() && file.isFile()) {\n" +
- " return new String(Files.readAllBytes(file.toPath()));\n" +
+ " if (Files.isRegularFile(filePath)) {\n" +
+ " Path realFilePath = filePath.toRealPath();\n" +
+ " if (!realFilePath.startsWith(basePath)) {\n" +
+ " return \"访问被拒绝:文件真实路径不合法\";\n" +
+ " }\n" +
+ " return new String(Files.readAllBytes(realFilePath));\n" +
" } else {\n" +
" return \"文件不存在或路径不正确:\" + fileName;\n" +
" }\n" +
"}"
// 任意文件类型-文件下载
-const downloadFile = '@ApiOperation(value = "下载文件", notes = "下载指定文件")\n' +
- '@RequestMapping("/downloadFile")\n' +
- 'public void downloadFile(@RequestParam("fileName") String fileName, HttpServletResponse response) throws IOException {\n' +
+const downloadFile = 'public void vul(String fileName, HttpServletResponse response) throws IOException {\n' +
' File file = new File(fileName);\n' +
'\n' +
' if (file.exists() && file.isFile()) {\n' +
@@ -840,14 +1084,49 @@ const downloadFile = '@ApiOperation(value = "下载文件", notes = "下载指
' response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在:" + fileName);\n' +
' }\n' +
'}'
+const safeDownloadFile = 'public void safe(String fileName,HttpServletResponse response) throws IOException {\n' +
+ ' String baseDir = sysConstant.getUploadFolder();\n' +
+ ' if (!isValidFileName(fileName)) {\n' +
+ ' response.sendError(HttpServletResponse.SC_BAD_REQUEST, "非法文件名:" + fileName);\n' +
+ ' return;\n' +
+ ' }\n' +
+ ' Path basePath = Paths.get(baseDir).toRealPath();\n' +
+ ' Path filePath = basePath.resolve(fileName).normalize();\n' +
+ '\n' +
+ ' if (filePath.startsWith(basePath) && Files.isRegularFile(filePath)) {\n' +
+ ' Path realFilePath = filePath.toRealPath();\n' +
+ ' if (!realFilePath.startsWith(basePath)) {\n' +
+ ' response.sendError(HttpServletResponse.SC_FORBIDDEN, "文件真实路径不合法:" + fileName);\n' +
+ ' return;\n' +
+ ' }\n' +
+ ' response.setContentType("application/octet-stream");\n' +
+ ' response.setHeader("Content-Disposition", "attachment; filename=\\"" + realFilePath.getFileName().toString() + "\\"");\n' +
+ ' try (InputStream fis = Files.newInputStream(realFilePath);\n' +
+ ' OutputStream os = response.getOutputStream()) {\n' +
+ ' StreamUtils.copy(fis, os);\n' +
+ ' os.flush();\n' +
+ ' ...\n' +
+ ' } else {\n' +
+ ' response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在:" + fileName);\n' +
+ ' }\n' +
+ '}'
// ssrf-服务端请求伪造
-const vul1URLConnection = "@ApiOperation(value = \"漏洞环境:服务端请求伪造\", notes = \"原生漏洞环境,未做任何限制,可调用URLConnection发起任意请求,探测内网服务、读取文件\")\n" +
- "@GetMapping(\"/vul1-URLConnection\")\n" +
- "public String vul1URLConnection(String url) {\n" +
+const vul1URLConnection = "@GetMapping(\"/internal/metadata\")\n" +
+ "public String internalMetadata() {\n" +
+ " return \"instance-id: i-javaseclab-ssrf ...\";\n" +
+ "}\n" +
+ "\n" +
+ "@GetMapping(\"/redirect\")\n" +
+ "public void redirect(String target, HttpServletResponse response) throws IOException {\n" +
+ " response.sendRedirect(target);\n" +
+ "}\n" +
+ "\n" +
+ "public String vul(String url) {\n" +
" try {\n" +
" URL u = new URL(url);\n" +
- " URLConnection conn = u.openConnection(); // 这里以URLConnection作为演示\n" +
+ " // URLConnection默认可请求file/http等协议,HTTP请求还可能自动跟随跳转\n" +
+ " URLConnection conn = u.openConnection();\n" +
" BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));\n" +
" String content;\n" +
" StringBuilder html = new StringBuilder();\n" +
@@ -862,38 +1141,63 @@ const vul1URLConnection = "@ApiOperation(value = \"漏洞环境:服务端请
" return e.getMessage();\n" +
" }\n" +
"}"
-const safe1WhiteList = "@ApiOperation(value = \"安全代码:请求白名单过滤\", notes = \"判断协议,对请求URL做白名单过滤\")\n" +
- "@GetMapping(\"/safe1-WhiteList\")\n" +
- "public String safe1WhiteList(@ApiParam(name = \"url\", value = \"请求参数\", required = true) @RequestParam String url) {\n" +
+const safe1WhiteList = "public String safe(String url) {\n" +
" if (!checkUserInput.isHttp(url)) {\n" +
" return \"检测到不是http(s)协议!\";\n" +
" } else if (!checkUserInput.ssrfWhiteList(url)) {\n" +
" return \"非白名单域名!\";\n" +
" } else {\n" +
+ " URL u = new URL(url);\n" +
+ " HttpURLConnection conn = (HttpURLConnection) u.openConnection();\n" +
+ " // 禁止自动跳转,每一跳都应重新校验协议、域名和IP\n" +
+ " conn.setInstanceFollowRedirects(false);\n" +
+ " conn.setConnectTimeout(3000);\n" +
+ " conn.setReadTimeout(3000);\n" +
" ...\n" +
" }\n" +
"}\n" +
- "// ssrf:判断http(s)协议\n" +
+ "// SSRF:判断http(s)协议,避免 startsWith 被空白、大小写、畸形URL等绕过\n" +
"public boolean isHttp(String url){\n" +
- " return url.startsWith(\"http://\") || url.startsWith(\"https://\");\n" +
+ " try {\n" +
+ " URI uri = new URI(url);\n" +
+ " String scheme = uri.getScheme();\n" +
+ " return \"http\".equalsIgnoreCase(scheme) || \"https\".equalsIgnoreCase(scheme);\n" +
+ " } catch (URISyntaxException e) {\n" +
+ " return false;\n" +
+ " }\n" +
"}\n" +
- "// ssrf:请求域名白名单\n" +
+ "// SSRF:请求域名白名单,同时校验解析后的IP\n" +
"public boolean ssrfWhiteList(String url) {\n" +
" List urlList = new ArrayList<>(Arrays.asList(\"baidu.com\", \"www.baidu.com\", \"whgojp.top\"));\n" +
" try {\n" +
- " URI uri = new URI(url.toLowerCase());\n" +
+ " URI uri = new URI(url);\n" +
" String host = uri.getHost();\n" +
- " return urlList.contains(host);\n" +
- " } catch (URISyntaxException e) {\n" +
+ " if (host == null || uri.getUserInfo() != null) {\n" +
+ " return false;\n" +
+ " }\n" +
+ " return urlList.contains(host.toLowerCase(Locale.ROOT)) && !isInternalHost(host);\n" +
+ " } catch (URISyntaxException | UnknownHostException e) {\n" +
" System.out.println(e);\n" +
" return false;\n" +
" }\n" +
+ "}\n" +
+ "\n" +
+ "private boolean isInternalHost(String host) throws UnknownHostException {\n" +
+ " InetAddress[] addresses = InetAddress.getAllByName(host);\n" +
+ " for (InetAddress address : addresses) {\n" +
+ " if (address.isAnyLocalAddress()\n" +
+ " || address.isLoopbackAddress()\n" +
+ " || address.isLinkLocalAddress()\n" +
+ " || address.isSiteLocalAddress()\n" +
+ " || address.isMulticastAddress()) {\n" +
+ " return true;\n" +
+ " }\n" +
+ " }\n" +
+ " return false;\n" +
"}"
// RCE
-const vulProcessBuilder = "@RequestMapping(\"/processBuilder\")\n" +
- "@ResponseBody\n" +
- "public R vulProcessBuilder(@RequestParam(\"payload\") String payload) throws IOException {\n" +
+const vulProcessBuilder = "public R vul1(String payload) throws IOException {\n" +
" String[] command = {\"sh\", \"-c\",payload};\n" +
"\n" +
" ProcessBuilder pb = new ProcessBuilder(command);\n" +
@@ -909,9 +1213,7 @@ const vulProcessBuilder = "@RequestMapping(\"/processBuilder\")\n" +
" return R.ok(output.toString());\n" +
"}"
-const vulGetRuntime = "@RequestMapping(\"/getRuntime\")\n" +
- "@ResponseBody\n" +
- "public R vulGetRuntime(String payload) throws IOException {\n" +
+const vulGetRuntime = "public R vul2(String payload) throws IOException {\n" +
" StringBuilder sb = new StringBuilder();\n" +
" String line;\n" +
" Process proc = Runtime.getRuntime().exec(payload);\n" +
@@ -923,9 +1225,7 @@ const vulGetRuntime = "@RequestMapping(\"/getRuntime\")\n" +
" }\n" +
" return R.ok(sb.toString());\n" +
"}"
-const vulProcessImpl = "@RequestMapping(\"/processImpl\")\n" +
- "@ResponseBody\n" +
- "public R vulProcessImpl(String payload) throws Exception {\n" +
+const vulProcessImpl = "public R vul3(String payload) throws Exception {\n" +
" // 获取 ProcessImpl 类对象\n" +
" Class> clazz = Class.forName(\"java.lang.ProcessImpl\");\n" +
"\n" +
@@ -943,17 +1243,35 @@ const vulProcessImpl = "@RequestMapping(\"/processImpl\")\n" +
" return R.ok(output.toString());\n" +
" }\n" +
"}"
-const safeProcessBuilder = "// 验证命令是否在允许的列表中\n" +
- "if (!ALLOWED_COMMANDS.contains(payload)) {\n" +
- " return R.error(\"不允许执行该命令!\");\n" +
+const safeProcessBuilder = "// 业务动作到固定命令参数的映射,用户不能直接控制命令字符串\n" +
+ "private static final Map> ALLOWED_COMMANDS = new HashMap<>();\n" +
+ "static {\n" +
+ " ALLOWED_COMMANDS.put(\"list\", Arrays.asList(\"ls\"));\n" +
+ " ALLOWED_COMMANDS.put(\"date\", Arrays.asList(\"date\"));\n" +
"}\n" +
"\n" +
- "// 可执行命令白名单\n" +
- "private static final List ALLOWED_COMMANDS = Arrays.asList(\"ls\", \"date\");"
+ "public R safe(String payload) throws IOException {\n" +
+ " List command = ALLOWED_COMMANDS.get(payload);\n" +
+ " if (command == null) {\n" +
+ " return R.error(\"不允许执行该动作!\");\n" +
+ " }\n" +
+ " ProcessBuilder pb = new ProcessBuilder(command);\n" +
+ " pb.redirectErrorStream(true);\n" +
+ " Process process = pb.start();\n" +
+ " try {\n" +
+ " if (!process.waitFor(3, TimeUnit.SECONDS)) {\n" +
+ " process.destroyForcibly();\n" +
+ " return R.error(\"命令执行超时!\");\n" +
+ " }\n" +
+ " } catch (InterruptedException e) {\n" +
+ " Thread.currentThread().interrupt();\n" +
+ " return R.error(\"命令执行被中断!\");\n" +
+ " }\n" +
+ " String output = readProcessOutput(process);\n" +
+ " return R.ok(output);\n" +
+ "}"
-const vulGroovy = "@GetMapping(\"/vulGroovy\")\n" +
- "@ResponseBody\n" +
- "public R vulGroovy(String payload) {\n" +
+const vulGroovy = "public R vulGroovy(String payload) {\n" +
" try {\n" +
" GroovyShell shell = new GroovyShell();\n" +
" Object result = shell.evaluate(payload); \n" +
@@ -980,40 +1298,22 @@ const vulGroovy = "@GetMapping(\"/vulGroovy\")\n" +
" }\n" +
" return output.toString();\n" +
"}"
-const safeGroovy = '@GetMapping("/safeGroovy")\n' +
- '@ResponseBody\n' +
- 'public R safeGroovy(String payload) {\n' +
- ' List trustedScripts = Arrays.asList(\n' +
- ' "\\"id\\".execute()",\n' +
- ' "\\"ls\\".execute()",\n' +
- ' "\\"whoami\\".execute()"\n' +
- ' );\n' +
- ' if (!isTrustedScript(payload, trustedScripts)) {\n' +
- ' return R.error("非法的脚本输入!");\n' +
+const safeGroovy = 'public R safeGroovy(String payload) {\n' +
+ ' if ("hello".equals(payload)) {\n' +
+ ' return R.ok("[+] 受控动作执行结果:Hello JavaSecLab");\n' +
' }\n' +
- ' try {\n' +
- ' GroovyShell shell = new GroovyShell();\n' +
- ' Object result = shell.evaluate(payload); \n' +
- ' if (result instanceof Process) {\n' +
- ' Process process = (Process) result;\n' +
- ' String output = getProcessOutput(process);\n' +
- ' return R.ok("[+] 执行受信任的脚本,结果:" + output);\n' +
- ' } else {\n' +
- ' return R.ok("[+] 执行受信任的脚本,结果:" + result.toString());\n' +
- ' }\n' +
- ' } catch (Exception e) {\n' +
- ' return R.error(e.getMessage());\n' +
+ ' if ("time".equals(payload)) {\n' +
+ ' return R.ok("[+] 受控动作执行结果:" + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));\n' +
' }\n' +
- '}\n' +
- 'private boolean isTrustedScript(String script, List trustedScripts) {\n' +
- ' return trustedScripts.contains(script);\n' +
- '}\n'
+ ' if ("sum".equals(payload)) {\n' +
+ ' return R.ok("[+] 受控动作执行结果:" + (1 + 2 + 3));\n' +
+ ' }\n' +
+ ' return R.error("非法的动作输入!");\n' +
+ '}'
// XXE
-const vulXMLReader = "@RequestMapping(value = \"/vulXMLReader\")\n" +
- "@ResponseBody\n" +
- "public String vulXMLReader(@RequestParam String payload) {\n" +
+const vulXMLReader = "public String vul1(String payload) {\n" +
" try {\n" +
" XMLReader xmlReader = XMLReaderFactory.createXMLReader();\n" +
" StringWriter stringWriter = new StringWriter();\n" +
@@ -1035,9 +1335,7 @@ const vulXMLReader = "@RequestMapping(value = \"/vulXMLReader\")\n" +
" }\n" +
"}"
-const vulSAXParser = "@RequestMapping(value = \"/vulSAXParser\")\n" +
- "@ResponseBody\n" +
- "public String vulSAXParser(@RequestParam String payload) {\n" +
+const vulSAXParser = "public String vul2(String payload) {\n" +
" try {\n" +
" SAXParserFactory factory = SAXParserFactory.newInstance();\n" +
" SAXParser parser = factory.newSAXParser();\n" +
@@ -1049,15 +1347,26 @@ const vulSAXParser = "@RequestMapping(value = \"/vulSAXParser\")\n" +
" }\n" +
"}"
-const safeXMLReader = "@RequestMapping(value = \"/safeXMLReader\")\n" +
- "@ResponseBody\n" +
- "public String safeXMLReader(@RequestParam String payload) {\n" +
+const vulDocumentBuilder = "public String vul3(String payload) {\n" +
+ " try {\n" +
+ " DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n" +
+ " DocumentBuilder builder = factory.newDocumentBuilder();\n" +
+ " Document document = builder.parse(new InputSource(new StringReader(payload)));\n" +
+ " return document.getDocumentElement().getTextContent();\n" +
+ " } catch (Exception e) {\n" +
+ " return e.toString();\n" +
+ " }\n" +
+ "}"
+
+const safeXMLReader = "public String safe1(String payload) {\n" +
" try {\n" +
" XMLReader xmlReader = XMLReaderFactory.createXMLReader();\n" +
" // 禁用外部实体引用,防止XXE攻击\n" +
" xmlReader.setFeature(\"http://apache.org/xml/features/disallow-doctype-decl\", true);\n" +
" xmlReader.setFeature(\"http://xml.org/sax/features/external-general-entities\", false);\n" +
" xmlReader.setFeature(\"http://xml.org/sax/features/external-parameter-entities\", false);\n" +
+ " xmlReader.setFeature(\"http://apache.org/xml/features/nonvalidating/load-external-dtd\", false);\n" +
+ " xmlReader.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader(\"\")));\n" +
" ...\n" +
" xmlReader.parse(new InputSource(new StringReader(payload)));\n" +
" return stringWriter.toString();\n" +
@@ -1065,9 +1374,32 @@ const safeXMLReader = "@RequestMapping(value = \"/safeXMLReader\")\n" +
" return e.getMessage();\n" +
" }\n" +
"}"
-const safeBlackList = "@RequestMapping(value = \"/safeBlackList\")\n" +
- "@ResponseBody\n" +
- "public String safeBlackList(@RequestParam String payload) {\n" +
+const safeDocumentBuilder = "public String safe3(String payload) {\n" +
+ " try {\n" +
+ " DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n" +
+ " factory.setFeature(\"http://apache.org/xml/features/disallow-doctype-decl\", true);\n" +
+ " factory.setFeature(\"http://xml.org/sax/features/external-general-entities\", false);\n" +
+ " factory.setFeature(\"http://xml.org/sax/features/external-parameter-entities\", false);\n" +
+ " factory.setFeature(\"http://apache.org/xml/features/nonvalidating/load-external-dtd\", false);\n" +
+ " factory.setXIncludeAware(false);\n" +
+ " factory.setExpandEntityReferences(false);\n" +
+ " setAttributeIfSupported(factory, XMLConstants.ACCESS_EXTERNAL_DTD, \"\");\n" +
+ " setAttributeIfSupported(factory, XMLConstants.ACCESS_EXTERNAL_SCHEMA, \"\");\n" +
+ " DocumentBuilder builder = factory.newDocumentBuilder();\n" +
+ " builder.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader(\"\")));\n" +
+ " ...\n" +
+ " } catch (Exception e) {\n" +
+ " return e.toString();\n" +
+ " }\n" +
+ "}\n" +
+ "private void setAttributeIfSupported(DocumentBuilderFactory factory, String name, String value) {\n" +
+ " try {\n" +
+ " factory.setAttribute(name, value);\n" +
+ " } catch (IllegalArgumentException ignored) {\n" +
+ " }\n" +
+ "}"
+const safeBlackList = "// 黑名单只能作为辅助检测,不应替代解析器安全配置\n" +
+ "public String safe2(String payload) {\n" +
" String[] black_list = {\"ENTITY\", \"DOCTYPE\"};\n" +
" for (String keyword : black_list) {\n" +
" if (payload.toUpperCase().contains(keyword)) {\n" +
@@ -1077,64 +1409,331 @@ const safeBlackList = "@RequestMapping(value = \"/safeBlackList\")\n" +
" return \"[-]XML内容安全\";\n" +
"}"
-// 水洞系列
+// 漏洞漏洞
+
+// 验证码安全
+const vul1Graphic = "public Boolean verifyCaptcha(String captchaInput, HttpSession session) {\n" +
+ " String sessionCaptcha = (String) session.getAttribute(\"vulCaptcha\");\n" +
+ " Long captchaCreationTime = (Long) session.getAttribute(\"captchaCreationTime\");\n" +
+ " // 如果没有验证码或生成时间,返回失败\n" +
+ " if (sessionCaptcha == null || captchaCreationTime == null) {\n" +
+ " return false;\n" +
+ " }\n" +
+ " // 验证码有效期为300秒(5分钟)5分钟可以无限制爆破账号密码\n" +
+ " long captchaExpiryTime = 300 * 1000; // 300秒转换为毫秒\n" +
+ " // 检查验证码是否过期\n" +
+ " if (System.currentTimeMillis() - captchaCreationTime > captchaExpiryTime) {\n" +
+ " session.removeAttribute(\"vulCaptcha\");\n" +
+ " session.removeAttribute(\"captchaCreationTime\");\n" +
+ " return false;\n" +
+ " }\n" +
+ " // 验证输入的验证码 这里验证失败后也没有清除旧的验证码\n" +
+ " if (sessionCaptcha.equalsIgnoreCase(captchaInput)) {\n" +
+ " return true;\n" +
+ " } else {\n" +
+ " return false;\n" +
+ " }\n" +
+ "}"
+const vul2Graphic = "public R vul2(String username, String password, String captcha,HttpSession session) {\n" +
+ "\tString sessionCaptcha = (String) session.getAttribute(\"vulCaptcha\");\n" +
+ "\t// 万能验证码:6666\n" +
+ "\tif (\"6666\".equals(captcha) || (sessionCaptcha != null && sessionCaptcha.equalsIgnoreCase(captcha))) {\n" +
+ "\t\t// 及时清除旧验证码\n" +
+ "\t\tsession.removeAttribute(\"vulCaptcha\");\n" +
+ "\t\tif (REAL_USERNAME.equals(username) && REAL_PASSWORD.equals(password)) {\n" +
+ "\t\t\treturn R.ok(\"账号爆破成功!用户名:\" + username + \",密码:\" + password);\n" +
+ "\t\t}else return R.error(\"账号或密码错误!\");\n" +
+ "\t}else {\n" +
+ "\t\tsession.removeAttribute(\"vulCaptcha\");\n" +
+ "\t\treturn R.error(\"验证码错误!\");\n" +
+ "\t}\n" +
+ "}"
+const vul3Graphic = "public R vul3(String username, String password, String captcha, HttpSession session) {\n" +
+ "\tString sessionCaptcha = (String) session.getAttribute(\"vulCaptcha\");\n" +
+ "\tif (sessionCaptcha != null && sessionCaptcha.equalsIgnoreCase(captcha)) {\n" +
+ "\t\tsession.removeAttribute(\"vulCaptcha\");\n" +
+ "\t\tif (REAL_USERNAME.equals(username) && REAL_PASSWORD.equals(password)) {\n" +
+ "\t\t\treturn R.ok(\"账号爆破成功!用户名:\" + username + \",密码:\" + password);\n" +
+ "\t\t} else return R.error(\"账号或密码错误!\");\n" +
+ "\t} else {\n" +
+ "\t\tsession.removeAttribute(\"vulCaptcha\");\n" +
+ "\t\treturn R.error(\"验证码错误!\");\n" +
+ "\t}\n" +
+ "}"
+const safeGraphic = "public R safe(String username, String password, String captcha, HttpSession session) {\n" +
+ " String sessionCaptcha = (String) session.getAttribute(\"safeCaptcha\");\n" +
+ " Long captchaTimestamp = (Long) session.getAttribute(\"captchaTimestamp\");\n" +
+ " // 验证验证码是否已失效(1分钟有效)\n" +
+ " if (captchaTimestamp == null || System.currentTimeMillis() - captchaTimestamp > 60 * 1000) {\n" +
+ " session.removeAttribute(\"safeCaptcha\");\n" +
+ " session.removeAttribute(\"captchaTimestamp\");\n" +
+ " return R.error(\"验证码已失效,请重新获取!\");\n" +
+ " }\n" +
+ " if (sessionCaptcha != null && sessionCaptcha.equalsIgnoreCase(captcha)) {\n" +
+ " session.removeAttribute(\"safeCaptcha\");\n" +
+ " session.removeAttribute(\"captchaTimestamp\");\n" +
+ " if (REAL_USERNAME.equals(username) && REAL_PASSWORD.equals(password)) {\n" +
+ " return R.ok(\"登录成功!用户名:\" + username + \",密码:\" + password);\n" +
+ " } else {\n" +
+ " return R.error(\"账号或密码错误!\");\n" +
+ " }\n" +
+ " } else {\n" +
+ " session.removeAttribute(\"safeCaptcha\");\n" +
+ " session.removeAttribute(\"captchaTimestamp\");\n" +
+ " return R.error(\"验证码错误,请重新输入!\");\n" +
+ " }\n" +
+ "}\n" +
+ "\n" +
+ "// 设置图形验证码长度6位\n" +
+ "ShearCaptcha shearCaptcha = CaptchaUtil.createShearCaptcha(90, 30, 6, 3);"
+
+const vul1SMS = "public R code(String phone, HttpSession session) {\n" +
+ " ...\n" +
+ " Random random = new Random();\n" +
+ " // 随机生成6位数验证码\n" +
+ " String captcha = String.valueOf(100000 + random.nextInt(900000));\n" +
+ " session.setAttribute(\"phone\", phone);\n" +
+ " session.setAttribute(\"smsCode\", captcha);\n" +
+ " session.setAttribute(\"captchaTimestamp\", System.currentTimeMillis());\n" +
+ " // 错误的将短信验证码回显在响应包中\n" +
+ " return R.ok(\"发送验证码成功!\" + captcha);\n" +
+ "}"
+const vul2SMS = "public R vul2(String phone, String code, @RequestParam(required = false, defaultValue = \"false\") boolean code_verify, HttpSession session) {\n" +
+ " ...\n" +
+ " // 校验code_verify字段,如果为true则验证登录成功\n" +
+ " if (code_verify){\n" +
+ " return R.ok(\"验证通过!用户:\"+phone);\n" +
+ " }\n" +
+ " if (!sessionCaptcha.equals(code)) {\n" +
+ " return R.error(\"验证码错误,请重新输入!\");\n" +
+ " }\n" +
+ " ...\n" +
+ " return R.ok(\"验证通过!用户:\"+phone);\n" +
+ "}"
+
+
+// 越权漏洞
+const vulHorizon = "public R vul(String username){\n" +
+ " User user = userMapper.getAllByUsername(username);\n" +
+ " if (user!=null){\n" +
+ " return R.ok(\"用户名:\"+user.getUsername()+\" 密码:\"+user.getPassword());\n" +
+ " }else return R.error(\"用户名不存在\");\n" +
+ "}"
+const safeHorizon = "public R safe(String username){\n" +
+ " // 获取当前登录的用户名\n" +
+ " String currentUsername = SecurityContextHolder.getContext().getAuthentication().getName();\n" +
+ " // 检查当前请求的用户名是否和登录用户名一致\n" +
+ " if (username == null || !username.equals(currentUsername)) {\n" +
+ " return R.error(\"您没有权限查看该用户的资料,当前登录用户:\"+currentUsername);\n" +
+ " }\n" +
+ " // 查询用户信息\n" +
+ " User user = userMapper.getAllByUsername(username);\n" +
+ " if (user != null) {\n" +
+ " return R.ok(\"用户名:\"+user.getUsername()+\" 密码:\"+user.getPassword());\n" +
+ " } else {\n" +
+ " return R.error(\"用户名不存在\");\n" +
+ " }\n" +
+ "}"
+const vulVertical = "public String vul() {\n" +
+ " // 漏洞点:只要知道管理员功能地址即可直接访问,没有做服务端角色校验。\n" +
+ " return \"vul/logic/idor/admin\";\n" +
+ "}"
+
+// 支付漏洞
+const vul1Pay = "public R vul1(@RequestParam String count, @RequestParam String price) {\n" +
+ " try {\n" +
+ " double totalPrice = Integer.parseInt(count) * Double.parseDouble(price);\n" +
+ " log.info(\"用户需支付金额:\" + totalPrice);\n" +
+ " \n" +
+ " // 直接使用客户端传入的价格,未与服务端商品实际价格进行校验\n" +
+ " BigDecimal currentMoney = userMoney.get();\n" +
+ " if (currentMoney.compareTo(BigDecimal.valueOf(totalPrice)) < 0) {\n" +
+ " return R.error(\"支付金额不足,支付失败!\");\n" +
+ " }\n" +
+ " userMoney.set(currentMoney.subtract(BigDecimal.valueOf(totalPrice)));\n" +
+ " return R.ok(\"支付成功!剩余余额:\" + userMoney.get());\n" +
+ " } catch (Exception e) {\n" +
+ " return R.error(e.toString());\n" +
+ " }\n" +
+ "}";
+
+const vul2Pay = "public R vul2(@RequestParam String orderId, @RequestParam double amount) {\n" +
+ " // 未检查订单是否已支付\n" +
+ " // 这里应该使用paymentStatusMap检查订单是否已支付,但为了演示漏洞,故意不检查\n" +
+ " BigDecimal currentMoney = userMoney.get();\n" +
+ " if (currentMoney.compareTo(BigDecimal.valueOf(amount)) < 0) {\n" +
+ " return R.error(\"余额不足\");\n" +
+ " }\n" +
+ " userMoney.set(currentMoney.subtract(BigDecimal.valueOf(amount)));\n" +
+ " return R.ok(\"支付成功!剩余余额:\" + userMoney.get());\n" +
+ "}";
+
+const vul3Pay = "public R vul3(@RequestParam String orderId, @RequestParam double amount) {\n" +
+ " // 模拟处理延迟\n" +
+ " try {\n" +
+ " Thread.sleep(1000);\n" +
+ " } catch (InterruptedException e) {\n" +
+ " Thread.currentThread().interrupt();\n" +
+ " }\n" +
+ "\n" +
+ " BigDecimal currentMoney = userMoney.get();\n" +
+ " if (currentMoney.compareTo(BigDecimal.valueOf(amount)) < 0) {\n" +
+ " return R.error(\"余额不足\");\n" +
+ " }\n" +
+ " userMoney.set(currentMoney.subtract(BigDecimal.valueOf(amount)));\n" +
+ " return R.ok(\"支付成功!剩余余额:\" + userMoney.get());\n" +
+ "}";
+
+const vulConcurrent = "public R vul(@RequestParam String orderId, @RequestParam double amount) {\n" +
+ " // 模拟业务处理耗时,扩大并发窗口\n" +
+ " Thread.sleep(1000);\n" +
+ "\n" +
+ " BigDecimal currentMoney = userMoney.get();\n" +
+ " BigDecimal payAmount = BigDecimal.valueOf(amount);\n" +
+ " if (currentMoney.compareTo(payAmount) < 0) {\n" +
+ " return R.error(\"余额不足\");\n" +
+ " }\n" +
+ " // 漏洞点:读取余额和写回余额不是一个原子操作,相同订单也没有幂等校验\n" +
+ " userMoney.set(currentMoney.subtract(payAmount));\n" +
+ " return R.ok(\"支付成功!订单:\" + orderId + \",剩余余额:\" + userMoney.get());\n" +
+ "}";
+
+const safeConcurrent = "public R safe(@RequestParam String orderId, @RequestParam double amount) {\n" +
+ " BigDecimal payAmount = BigDecimal.valueOf(amount);\n" +
+ " synchronized (paymentLock) {\n" +
+ " if (paidOrders.contains(orderId)) {\n" +
+ " return R.error(\"订单已支付,拒绝重复扣款:\" + orderId);\n" +
+ " }\n" +
+ " BigDecimal currentMoney = userMoney.get();\n" +
+ " if (currentMoney.compareTo(payAmount) < 0) {\n" +
+ " return R.error(\"余额不足\");\n" +
+ " }\n" +
+ " paidOrders.add(orderId);\n" +
+ " userMoney.set(currentMoney.subtract(payAmount));\n" +
+ " return R.ok(\"支付成功!订单:\" + orderId + \",剩余余额:\" + userMoney.get());\n" +
+ " }\n" +
+ "}";
+
+const vul4Pay = "@ApiOperation(\"支付流程绕过漏洞 - 创建订单\")\n" +
+ "@RequestMapping(\"/vul4/create\")\n" +
+ "public R createOrder(@RequestParam String orderId, @RequestParam double amount) {\n" +
+ " OrderStatus status = new OrderStatus(orderId, BigDecimal.valueOf(amount));\n" +
+ " orderStatusMap.put(orderId, status);\n" +
+ " Map data = new HashMap<>();\n" +
+ " data.put(\"orderId\", orderId);\n" +
+ " data.put(\"amount\", amount);\n" +
+ " return R.ok(\"订单创建成功\").put(\"data\", data);\n" +
+ "}\n" +
+ "\n" +
+ "@ApiOperation(\"支付流程绕过漏洞 - 查询订单状态\")\n" +
+ "@RequestMapping(\"/vul4/status\")\n" +
+ "public R getOrderStatus(@RequestParam String orderId) {\n" +
+ " OrderStatus status = orderStatusMap.get(orderId);\n" +
+ " if (status == null) {\n" +
+ " return R.error(\"订单不存在\");\n" +
+ " }\n" +
+ " Map data = new HashMap<>();\n" +
+ " data.put(\"orderId\", status.orderId);\n" +
+ " data.put(\"amount\", status.amount);\n" +
+ " data.put(\"isPaid\", status.isPaid);\n" +
+ " return R.ok().put(\"data\", data);\n" +
+ "}\n" +
+ "\n" +
+ "@ApiOperation(\"支付流程绕过漏洞 - 支付通知\")\n" +
+ "@RequestMapping(\"/vul4/notify\")\n" +
+ "public R paymentNotify(@RequestParam String orderId, @RequestParam boolean success) {\n" +
+ " // 未验证通知来源,直接更新订单状态\n" +
+ " OrderStatus status = orderStatusMap.get(orderId);\n" +
+ " if (status == null) {\n" +
+ " return R.error(\"订单不存在\");\n" +
+ " }\n" +
+ " status.isPaid = success;\n" +
+ " return R.ok(\"状态更新成功\");\n" +
+ "}";
+const vul5Pay = "public R integerOverflow(@RequestParam String count, @RequestParam String price) {\n" +
+ " try {\n" +
+ " Integer countValue = Integer.valueOf(count);\n" +
+ " Integer priceValue = Integer.valueOf(price);\n" +
+ "\n" +
+ " // 整数溢出场景:当 count 或 price 数值过大时,可能会导致溢出\n" +
+ " int totalAmount = countValue * priceValue;\n" +
+ " log.info(\"用户需支付金额:\" + totalAmount);\n" +
+ "\n" +
+ " BigDecimal currentMoney = userMoney.get();\n" +
+ " if (currentMoney.compareTo(BigDecimal.valueOf(totalAmount)) < 0) {\n" +
+ " return R.error(\"支付金额不足,支付失败!\");\n" +
+ " }\n" +
+ " userMoney.set(currentMoney.subtract(BigDecimal.valueOf(totalAmount)));\n" +
+ " return R.ok(\"支付成功!剩余余额:\" + userMoney.get());\n" +
+ " } catch (Exception e) {\n" +
+ " return R.error(\"无效的输入,请输入有效的数量和价格!\");\n" +
+ " }\n" +
+ "}";
+const vul6Pay = "public R floatingPointPrecision(@RequestParam String count, @RequestParam String price) {\n" +
+ " try {\n" +
+ " double totalAmount = Double.parseDouble(count) * Double.parseDouble(price);\n" +
+ " // 漏洞点:把二进制浮点计算结果直接转成金额,可能引入精度误差\n" +
+ " BigDecimal amountValue = new BigDecimal(totalAmount);\n" +
+ " log.info(\"用户需支付金额:\" + amountValue);\n" +
+ "\n" +
+ " BigDecimal currentMoney = userMoney.get();\n" +
+ " if (currentMoney.compareTo(amountValue) < 0) {\n" +
+ " return R.error(\"支付金额不足,支付失败!\");\n" +
+ " }\n" +
+ " userMoney.set(currentMoney.subtract(amountValue));\n" +
+ " return R.ok(\"支付成功!实际扣款金额:\" + amountValue + \",剩余余额:\" + userMoney.get());\n" +
+ " } catch (Exception e) {\n" +
+ " return R.error(\"无效的输入,请输入有效的数量和价格!\");\n" +
+ " }\n" +
+ "}";
+
+// 其他漏洞
const vul1SpringMvcRedirect = "// 基于Spring MVC的重定向方式\n" +
"// 通过返回带有 redirect: 前缀的字符串来实现重定向。\n" +
- "@GetMapping(\"/redirect\")\n" +
- "public String vul1SpringMvc(@RequestParam(\"url\") String url) {\n" +
+ "public String vul1(@RequestParam(\"url\") String url) {\n" +
" return \"redirect:\" + url; // Spring MVC写法 302临时重定向\n" +
"}\n" +
"\n" +
"// 通过返回 ModelAndView 对象并指定 redirect: 前缀来实现重定向。\n" +
- "@RequestMapping(\"/redirectWithModelAndView\")\n" +
- "public ModelAndView vul1ModelAndView(@RequestParam(\"url\") String url) {\n" +
+ "public ModelAndView vul2(@RequestParam(\"url\") String url) {\n" +
" return new ModelAndView(\"redirect:\" + url); // Spring MVC写法 使用ModelAndView 302临时重定向\n" +
"}";
const vul2ServletRedirect = "// 基于Servlet标准的重定向方式\n" +
"// 通过设置响应状态码和头部信息实现重定向。\n" +
- "@RequestMapping(\"/setHeader\")\n" +
- "@ResponseBody\n" +
- "public static void vul2setHeader(HttpServletRequest request, HttpServletResponse response) {\n" +
+ "public static void vul2(HttpServletRequest request, HttpServletResponse response) {\n" +
" String url = request.getParameter(\"url\");\n" +
" response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301永久重定向\n" +
" response.setHeader(\"Location\", url);\n" +
"}\n" +
"\n" +
"// 通过调用 HttpServletResponse.sendRedirect() 实现重定向。\n" +
- "@RequestMapping(\"/sendRedirect\")\n" +
- "@ResponseBody\n" +
- "public static void vul2sendRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {\n" +
+ "public static void vul3(HttpServletRequest request, HttpServletResponse response) throws IOException {\n" +
" String url = request.getParameter(\"url\");\n" +
" response.sendRedirect(url); // 302临时重定向\n" +
"}";
const vul3SpringRedirect = "// 基于Spring注解和状态码的重定向方式\n" +
"// 使用ResponseEntity设置状态码实现重定向\n" +
- "@RequestMapping(\"/responseEntityRedirect\")\n" +
- "@ResponseBody\n" +
- "public ResponseEntity responseEntityRedirect(@RequestParam(\"url\") String url) {\n" +
+ "public ResponseEntity vul5(@RequestParam(\"url\") String url) {\n" +
" HttpHeaders headers = new HttpHeaders();\n" +
" headers.setLocation(URI.create(url));\n" +
" return new ResponseEntity<>(headers, HttpStatus.FOUND); // 302临时重定向\n" +
"}\n" +
"\n" +
"// 通过注解设置状态码实现重定向\n" +
- "@GetMapping(\"/annotationRedirect\")\n" +
"@ResponseStatus(HttpStatus.FOUND) // 302临时重定向\n" +
- "public void annotationRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {\n" +
+ "public void vul6(HttpServletRequest request, HttpServletResponse response) throws IOException {\n" +
" String url = request.getParameter(\"url\");\n" +
" response.setHeader(\"Location\", url);\n" +
"}";
const safe1Forward = "// 内部跳转\n" +
- "@RequestMapping(\"/forward\")\n" +
- "@ResponseBody\n" +
- "public static void safe1Forward(HttpServletRequest request, HttpServletResponse response) {\n" +
+ "public static void safe1(HttpServletRequest request, HttpServletResponse response) {\n" +
" String url = request.getParameter(\"url\");\n" +
" RequestDispatcher rd = request.getRequestDispatcher(url);\n" +
" try {\n" +
+ " // 做了内部转发\n" +
" rd.forward(request, response);\n" +
- " log.info(\"做了内部转发……\");\n" +
" } catch (Exception e) {\n" +
" e.printStackTrace();\n" +
" }\n" +
@@ -1158,8 +1757,7 @@ const safe2CheckUrl = '// 定义 URL 白名单\n' +
' }\n' +
' return true;\n' +
'}\n';
-const vulXffforgery = "@RequestMapping(\"/buffli\")\n" +
- "public String buffli(HttpServletRequest request, Model model) {\n" +
+const vulXffforgery = "public String vul1(HttpServletRequest request, Model model) {\n" +
" // 前后端不分离 使用request.getRemoteHost()获取客户端IP\n" +
" final String remoteHost = request.getRemoteHost();\n" +
" boolean isClientIP8888 = \"8.8.8.8\".equals(remoteHost);\n" +
@@ -1171,8 +1769,7 @@ const vulXffforgery = "@RequestMapping(\"/buffli\")\n" +
" return \"vul/other/onlyForGoogle\";\n" +
"}\n" +
"\n" +
- "@RequestMapping(\"/ffli\")\n" +
- "public String ffli(HttpServletRequest request, HttpServletResponse response, Model model, String xff) {\n" +
+ "public String vul2(HttpServletRequest request, HttpServletResponse response, Model model, String xff) {\n" +
" // 前后端分离 模拟通过X-Forwarded-For头获取客户端IP\n" +
" String remoteHost = \"\";\n" +
" if (xff.equals(\"true\")) {\n" +
@@ -1190,24 +1787,35 @@ const vulXffforgery = "@RequestMapping(\"/buffli\")\n" +
" return \"vul/other/onlyForGoogle\";\n" +
"}";
-const safeXffforgery = "@RequestMapping(\"/safe\")\n" +
- "public String safe(HttpServletRequest request, HttpServletResponse response, Model model, String xff){\n" +
- " ...\n" +
- " if (!isTrustedProxy(remoteHost)){\n" +
- " model.addAttribute(\"clientIP\", request.getRemoteAddr());\n" +
- " model.addAttribute(\"sensitiveInfo\", \"源ip不在白名单范围内!\");\n" +
+const safeXffforgery = "public String safe(HttpServletRequest request, HttpServletResponse response, Model model, String xff){\n" +
+ " String proxyIp = request.getRemoteAddr();\n" +
+ " String remoteHost = proxyIp;\n" +
+ " if (\"true\".equals(xff)) {\n" +
+ " if (!isTrustedProxy(proxyIp)){\n" +
+ " model.addAttribute(\"clientIP\", proxyIp);\n" +
+ " model.addAttribute(\"sensitiveInfo\", \"非可信代理来源,忽略XFF头:\" + proxyIp);\n" +
+ " return \"vul/other/onlyForGoogle\";\n" +
+ " }\n" +
+ " remoteHost = getFirstForwardedIp(request.getHeader(\"X-Forwarded-For\"));\n" +
+ " }\n" +
+ " if (remoteHost == null || remoteHost.isEmpty()) {\n" +
+ " model.addAttribute(\"clientIP\", proxyIp);\n" +
+ " model.addAttribute(\"sensitiveInfo\", \"XFF头为空或格式异常!\");\n" +
" return \"vul/other/onlyForGoogle\";\n" +
" }\n" +
- " ...\n" +
+ " boolean isClientIP8888 = \"8.8.8.8\".equals(remoteHost);\n" +
+ " model.addAttribute(\"clientIP\", remoteHost);\n" +
+ " if (isClientIP8888) {\n" +
+ " model.addAttribute(\"sensitiveInfo\", \"username:admin,password:Admin123\");\n" +
+ " }\n" +
+ " return \"vul/other/onlyForGoogle\";\n" +
"}\n" +
"// 判断是否来自可信代理\n" +
"private boolean isTrustedProxy(String ip) {\n" +
- " return Arrays.asList(\"127.0.0.1\", \"192.168.1.1\", \"10.0.0.1\").contains(ip);\n" +
+ " return Arrays.asList(\"192.168.1.1\", \"10.0.0.1\").contains(ip);\n" +
"}"
-const vulCsrf = "@RequestMapping(\"/vul\")\n" +
- "@ResponseBody\n" +
- "public R vulCsrf(String receiver, String amount, @AuthenticationPrincipal UserDetails userDetails){\n" +
+const vulCsrf = "public R vul(String receiver, String amount, @AuthenticationPrincipal UserDetails userDetails){\n" +
" String currentUser = userDetails.getUsername();\n" +
" Map result = new HashMap<>();\n" +
" result.put(\"currentUser\", currentUser);\n" +
@@ -1215,14 +1823,12 @@ const vulCsrf = "@RequestMapping(\"/vul\")\n" +
" result.put(\"amount\", amount);\n" +
" return R.ok(result);\n" +
"}"
-const safeCsrfToken = "@GetMapping(\"/safe\")\n" +
- "@ResponseBody\n" +
- "public Map safeCsrf(@RequestParam(\"receiver\") String receiver,@RequestParam(\"amount\") String amount,@AuthenticationPrincipal UserDetails userDetails,@RequestParam(\"csrfToken\") String csrfToken,HttpSession session) {\n" +
+const safeCsrfToken = "public Map safeCsrf(String receiver,String amount,@AuthenticationPrincipal UserDetails userDetails,String csrfToken,HttpSession session) {\n" +
" String currentUser = userDetails.getUsername();\n" +
"\n" +
" String sessionToken = (String) session.getAttribute(\"csrfToken\");\n" +
" Map result = new HashMap<>();\n" +
- " if (!csrfToken.equals(sessionToken)) {\n" +
+ " if (!constantTimeEquals(csrfToken, sessionToken)) {\n" +
" result.put(\"success\", false);\n" +
" result.put(\"message\", \"Token失效!\");\n" +
" return result;\n" +
@@ -1232,29 +1838,53 @@ const safeCsrfToken = "@GetMapping(\"/safe\")\n" +
" result.put(\"amount\", amount);\n" +
" result.put(\"csrfToken\", csrfToken);\n" +
" return result;\n" +
+ "}\n" +
+ "\n" +
+ "private boolean constantTimeEquals(String requestToken, String sessionToken) {\n" +
+ " if (requestToken == null || sessionToken == null) {\n" +
+ " return false;\n" +
+ " }\n" +
+ " return MessageDigest.isEqual(\n" +
+ " requestToken.getBytes(StandardCharsets.UTF_8),\n" +
+ " sessionToken.getBytes(StandardCharsets.UTF_8)\n" +
+ " );\n" +
"}"
-const safeCsrfReferer = "@GetMapping(\"/safe2\")\n" +
- "@ResponseBody\n" +
- "public Map safeCsrf(HttpServletRequest request, @RequestParam(\"receiver\") String receiver, @RequestParam(\"amount\") String amount, @AuthenticationPrincipal UserDetails userDetails, HttpSession session) {\n" +
+const safeCsrfReferer = "public Map safe2(HttpServletRequest request,String receiver,String amount, @AuthenticationPrincipal UserDetails userDetails, HttpSession session) {\n" +
" String currentUser = userDetails.getUsername();\n" +
" Map result = new HashMap<>();\n" +
- " String referer = request.getHeader(\"referer\");\n" +
- " if (referer == null || !referer.startsWith(\"http://127.0.0.1\")) {\n" +
+ " String originOrReferer = request.getHeader(\"Origin\");\n" +
+ " if (originOrReferer == null) {\n" +
+ " originOrReferer = request.getHeader(\"Referer\");\n" +
+ " }\n" +
+ " if (!isTrustedSameOrigin(request, originOrReferer)) {\n" +
" result.put(\"success\", false);\n" +
- " result.put(\"message\", \"referer无效!\");\n" +
+ " result.put(\"message\", \"Origin/Referer无效!\");\n" +
" return result;\n" +
" }\n" +
" result.put(\"currentUser\", currentUser);\n" +
" result.put(\"receiver\", receiver);\n" +
" result.put(\"amount\", amount);\n" +
" return result;\n" +
+ "}\n" +
+ "\n" +
+ "private boolean isTrustedSameOrigin(HttpServletRequest request, String originOrReferer) {\n" +
+ " if (originOrReferer == null) {\n" +
+ " return false;\n" +
+ " }\n" +
+ " try {\n" +
+ " URI uri = new URI(originOrReferer);\n" +
+ " int actualPort = uri.getPort() == -1 ? defaultPort(uri.getScheme()) : uri.getPort();\n" +
+ " return request.getScheme().equalsIgnoreCase(uri.getScheme())\n" +
+ " && request.getServerName().equalsIgnoreCase(uri.getHost())\n" +
+ " && request.getServerPort() == actualPort;\n" +
+ " } catch (URISyntaxException e) {\n" +
+ " return false;\n" +
+ " }\n" +
"}"
// 跨域安全问题
-const vulCORS = "@GetMapping(\"/corsVul\")\n" +
- "@ResponseBody\n" +
- "public String corsVul(HttpServletRequest request, HttpServletResponse response) {\n" +
- " String origin = request.getHeader(\"origin\");\n" +
+const vulCORS = "public R vul(HttpServletRequest request, HttpServletResponse response) {\n" +
+ " String origin = request.getHeader(\"Origin\");\n" +
"\n" +
" if (origin != null) {\n" +
" response.setHeader(\"Access-Control-Allow-Origin\", origin);\n" +
@@ -1265,24 +1895,36 @@ const vulCORS = "@GetMapping(\"/corsVul\")\n" +
" // 允许携带 Cookie 或其他凭证\n" +
" response.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n" +
" response.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, PUT, DELETE, OPTIONS\");\n" +
+ " response.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization, X-Requested-With\");\n" +
+ " response.setHeader(\"Vary\", \"Origin\");\n" +
"\n" +
- " return \"CORS漏洞演示:username:admin,password:Admin123\";\n" +
+ " return R.ok(\"CORS漏洞演示:username:admin,password:Admin123\");\n" +
"}"
-const safeCORS = "@CrossOrigin(origins = {\"http://127.0.0.1:8080\", \"https://127.0.0.1:8080\"}, allowCredentials = \"true\")\n" +
- "@GetMapping(\"/corsSafe\")\n" +
- "@ResponseBody\n" +
- "public String corsSafe(HttpServletRequest request, HttpServletResponse response) {\n" +
- " // 记录安全 CORS 请求来源\n" +
- " String origin = request.getHeader(\"origin\");\n" +
- " // 允许携带凭证,但前提是 `Access-Control-Allow-Origin` 与可信来源匹配\n" +
+const safeCORS = "private static final Set TRUSTED_ORIGINS = new HashSet<>(Arrays.asList(\n" +
+ " \"http://127.0.0.1:8080\",\n" +
+ " \"https://127.0.0.1:8080\"\n" +
+ "));\n" +
+ "\n" +
+ "public R safe(HttpServletRequest request, HttpServletResponse response) {\n" +
+ " String origin = request.getHeader(\"Origin\");\n" +
+ " response.setHeader(\"Vary\", \"Origin\");\n" +
+ " if (origin == null) {\n" +
+ " return R.ok(\"同源请求不需要CORS响应头\");\n" +
+ " }\n" +
+ " if (!TRUSTED_ORIGINS.contains(origin)) {\n" +
+ " response.setStatus(HttpServletResponse.SC_FORBIDDEN);\n" +
+ " return R.error(HttpServletResponse.SC_FORBIDDEN, \"Origin不在CORS白名单\");\n" +
+ " }\n" +
+ " response.setHeader(\"Access-Control-Allow-Origin\", origin);\n" +
" response.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n" +
+ " response.setHeader(\"Access-Control-Allow-Methods\", \"GET, OPTIONS\");\n" +
+ " response.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\");\n" +
"\n" +
- " return \"配置CORS可信源白名单\";\n" +
- "}"
+ " return R.ok(\"配置CORS可信源白名单\");\n" +
+ "}\n"
-const vulJSONP = '@GetMapping("/jsonpVul")\n' +
- 'public void jsonpVul(HttpServletRequest request, HttpServletResponse response) throws IOException, java.io.IOException {\n' +
+const vulJSONP = 'public void vul(HttpServletRequest request, HttpServletResponse response) throws IOException {\n' +
' String callback = request.getParameter("callback");\n' +
' String sensitiveData = "{\\"username\\":\\"admin\\",\\"password\\":\\"Admin123\\"}";\n' +
'\n' +
@@ -1290,16 +1932,110 @@ const vulJSONP = '@GetMapping("/jsonpVul")\n' +
' String jsonpResponse = callback + "(" + sensitiveData + ");";\n' +
'\n' +
' // 设置响应类型为 JavaScript 脚本\n' +
- ' response.setContentType("application/javascript");\n' +
+ ' response.setContentType("application/javascript;charset=UTF-8");\n' +
' response.getWriter().write(jsonpResponse);\n' +
'}\n'
-const safeJSONP = "// 校验回调函数名是否合法\n" +
- "if (callback == null || !callback.matches(\"^[a-zA-Z_$][a-zA-Z0-9_$]*$\")) {\n" +
- " response.setStatus(HttpServletResponse.SC_BAD_REQUEST);\n" +
- " response.getWriter().write(\"Invalid callback\");\n" +
- " return;\n" +
+const safeJSONP = "private static final Pattern JSONP_CALLBACK_PATTERN = Pattern.compile(\n" +
+ " \"^[A-Za-z_$][A-Za-z0-9_$]*(\\\\.[A-Za-z_$][A-Za-z0-9_$]*)*$\"\n" +
+ ");\n" +
+ "\n" +
+ "public void safe(HttpServletRequest request, HttpServletResponse response) throws IOException {\n" +
+ " String callback = request.getParameter(\"callback\");\n" +
+ " // 校验回调函数名是否合法\n" +
+ " if (callback == null || !JSONP_CALLBACK_PATTERN.matcher(callback).matches()) {\n" +
+ " response.setStatus(HttpServletResponse.SC_BAD_REQUEST);\n" +
+ " response.getWriter().write(\"Invalid callback\");\n" +
+ " return;\n" +
+ " }\n" +
+ "\n" +
+ " String publicData = \"{\\\"message\\\":\\\"public data only\\\"}\";\n" +
+ " response.setContentType(\"application/javascript;charset=UTF-8\");\n" +
+ " response.setHeader(\"X-Content-Type-Options\", \"nosniff\");\n" +
+ " response.getWriter().write(callback + \"(\" + publicData + \");\");\n" +
+ "}"
+
+const vulDos = "public void vul(Integer width,Integer height,HttpServletResponse response) throws IOException {\n" +
+ " response.setContentType(\"image/jpeg\");\n" +
+ " response.setHeader(\"Pragma\", \"no-cache\");\n" +
+ " response.setHeader(\"Cache-Control\", \"no-cache\");\n" +
+ " // 验证码参数可控 造成拒绝服务攻击\n" +
+ " ShearCaptcha shearCaptcha = CaptchaUtil.createShearCaptcha(width, height,4,3);\n" +
+ " try {\n" +
+ " shearCaptcha.write(response.getOutputStream());\n" +
+ " } catch (IOException e) {\n" +
+ " throw new RuntimeException(e);\n" +
+ " }\n" +
+ "}"
+const safeDos = "public void safe(Integer width,Integer height,HttpServletResponse response) throws IOException {\n" +
+ " if (width == null || height == null || width <= 0 || height <= 0\n" +
+ " || width > MAX_IMAGE_WIDTH || height > MAX_IMAGE_HEIGHT\n" +
+ " || (long) width * height > MAX_IMAGE_PIXELS) {\n" +
+ " response.setStatus(HttpServletResponse.SC_BAD_REQUEST);\n" +
+ " response.setContentType(\"text/plain;charset=UTF-8\");\n" +
+ " response.getWriter().write(\"图片尺寸超出限制\");\n" +
+ " return;\n" +
+ " }\n" +
+ " response.setContentType(\"image/jpeg\");\n" +
+ " ShearCaptcha shearCaptcha = CaptchaUtil.createShearCaptcha(width, height, 4, 3);\n" +
+ " shearCaptcha.write(response.getOutputStream());\n" +
+ "}"
+const vul2Dos = "// 如果解压出的文件是ZIP文件,则递归解压\n" +
+ "if (entry.getName().endsWith(\".zip\")) {\n" +
+ " // 创建临时文件来存储这个ZIP\n" +
+ " File tempFile = File.createTempFile(\"unzip\", \".zip\");\n" +
+ " try (FileOutputStream fos = new FileOutputStream(tempFile)) {\n" +
+ " byte[] buffer = new byte[1024];\n" +
+ " int length;\n" +
+ " while ((length = zipInputStream.read(buffer)) != -1) {\n" +
+ " fos.write(buffer, 0, length);\n" +
+ " }\n" +
+ " }\n" +
+ " // 递归解压这个新的ZIP文件\n" +
+ " unzip(tempFile, currentDepth + 1, maxDepth);\n" +
+ " // 解压完成后删除临时文件\n" +
+ " tempFile.delete();\n" +
+ "} "
+
+const vulXpath = "public R vul(String username,String password) {\n" +
+ " try {\n" +
+ " // 构造XML数据\n" +
+ " String xmlData = \"admin password \";\n" +
+ " \n" +
+ "\t\t// 解析XML文档\n" +
+ " DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();\n" +
+ " Document doc = builder.parse(new InputSource(new StringReader(xmlData)));\n" +
+ "\n" +
+ " // 构造XPath表达式(存在注入漏洞)\n" +
+ " XPath xpath = XPathFactory.newInstance().newXPath();\n" +
+ " String expression = \"/users/user[username='\" + username + \"' and password='\" + password + \"']\";\n" +
+ " NodeList nodes = (NodeList) xpath.evaluate(expression, doc, XPathConstants.NODESET);\n" +
+ " if (nodes.getLength() > 0) {\n" +
+ " return R.ok(\"用户名和密码验证通过!\");\n" +
+ " } else {\n" +
+ " return R.ok(\"用户名或密码错误!\");\n" +
+ " }\n" +
+ " ...\n" +
"}"
+const safeXpath = "public R safe(String username,String password) {\n" +
+ " try {\n" +
+ " DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n" +
+ " DocumentBuilder builder = factory.newDocumentBuilder();\n" +
+ " String xml = \"admin password \";\n" +
+ " Document doc = builder.parse(new InputSource(new StringReader(xml)));\n" +
+ "\n" +
+ " XPath xpath = XPathFactory.newInstance().newXPath();\n" +
+ " xpath.setXPathVariableResolver(variableName -> resolveXPathVariable(variableName, username, password));\n" +
+ " String expression = \"/users/user[username=$username and password=$password]\";\n" +
+ " NodeList nodes = (NodeList) xpath.evaluate(expression, doc, XPathConstants.NODESET);\n" +
+ "\n" +
+ " if (nodes.getLength() > 0) {\n" +
+ " return R.ok(\"用户名和密码验证通过!欢迎:\" + username);\n" +
+ " } else {\n" +
+ " return R.error(\"认证失败:用户名或密码错误\");\n" +
+ " }\n" +
+ " ...\n" +
+ "}\n"
// js泄漏-硬编码
const hardCoding = "function login() {\n" +
@@ -1333,6 +2069,49 @@ const infoLeakJs = "var r = new i({\n" +
" data: {path: \"https://official-website-1305887643.cos.ap-beijing.myqcloud.com/\".concat(o)}\n" +
" })\n" +
"})";
+const infoLeakBackUp = "root@MacBook ~/www/JavaSecLab tree -L 4 -h -t\n" +
+ "[ 320] .\n" +
+ "├── [ 76] deploy.sh\n" +
+ "├── [ 281] Dockerfile\n" +
+ "├── [ 818] docker-compose.yml\n" +
+ "├── [ 11K] LICENSE\n" +
+ "├── [5.7K] pom.xml\n" +
+ "├── [ 96] sql\n" +
+ "│ └── [2.9K] JavaSecLab.sql\n" +
+ "└── [ 128] src\n" +
+ " └── [ 128] main\n" +
+ " ├── [ 96] java\n" +
+ " │ └── [ 96] top\n" +
+ " └── [ 320] resources\n" +
+ " ├── [ 273] banner.txt\n" +
+ " ├── [ 427] application-docker.yml\n" +
+ " ├── [9.4K] logback-spring.xml\n" +
+ " ├── [ 421] application-dev.yml\n" +
+ " ├── [ 420] application-prod.yml\n" +
+ " ├── [1.2K] application.yml\n" +
+ " └── [ 160] mapper\n" +
+ "\n" +
+ "8 directories, 12 files"
+const infoLeakLog = "// 开启了调试模式,打印了sql执行记录 并且输出了SessionId\n" +
+ "JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@784bb5e2] will not be managed by Spring\n" +
+ "==> Preparing: SELECT username,password FROM user WHERE (username = ?)\n" +
+ "==> Parameters: admin(String)\n" +
+ "2024-11-18 16:24:18 DEBUG Statement:136 - {conn-10006, pstmt-20183} Parameters : [admin]\n" +
+ "2024-11-18 16:24:18 DEBUG Statement:136 - {conn-10006, pstmt-20183} Types : [VARCHAR]\n" +
+ "2024-11-18 16:24:18 DEBUG Statement:136 - {conn-10006, pstmt-20183} executed. 2.459148 millis. SELECT username,password FROM user \n" +
+ " \n" +
+ " WHERE (username = ?)\n" +
+ "2024-11-18 16:24:18 DEBUG ResultSet:141 - {conn-10006, pstmt-20183, rs-50764} open\n" +
+ "2024-11-18 16:24:18 DEBUG ResultSet:141 - {conn-10006, pstmt-20183, rs-50764} Header: [username, password]\n" +
+ "2024-11-18 16:24:18 DEBUG ResultSet:141 - {conn-10006, pstmt-20183, rs-50764} Result: [admin, admin]\n" +
+ "<== Columns: username, password\n" +
+ "<== Row: admin, admin\n" +
+ "<== Total: 1\n" +
+ "Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@23f938fe]\n" +
+ "2024-11-18 16:24:18.822 INFO 1 --- [-nio-80-exec-41] RequestAwareAuthenticationSuccessHandler : 用户名:admin,于2024-11-18 16:24:18 成功登录系统 IP:123.118.108.249 session:WebAuthenticationDetails [RemoteIpAddress=123.118.108.249, SessionId=0170B66882476E34F35BC232051F63E0]\n" +
+ "2024-11-18 16:24:32.737 INFO 1 --- [-nio-80-exec-41] t.w.m.system.controller.LoginController : session id E3C352C378552D29E10BDF42647B6864, 生成的验证码 o1kY\n" +
+ "2024-11-18 16:24:44.127 INFO 1 --- [-nio-80-exec-34] t.w.m.system.controller.LoginController : session id E3C352C378552D29E10BDF42647B6864, 生成的验证码 7Cwn"
+
const springBootSwagger = "return new Docket(DocumentationType.OAS_30)\n" +
" .pathMapping(\"/\")\n" +
" .enable(swaggerProperties.getEnable())//生产禁用\n" +
@@ -1357,7 +2136,7 @@ const springBootSwagger = "return new Docket(DocumentationType.OAS_30)\n" +
const springBootActuator = "management:\n" +
" # 端点信息接口使用的端口,为了和主系统接口使用的端口进行分离\n" +
" server:\n" +
- " port: 8080\n" +
+ " port: 80\n" +
" # 端点健康情况,默认值\"never\",设置为\"always\"可以显示硬盘使用情况和线程情况\n" +
" endpoint:\n" +
" health:\n" +
@@ -1387,23 +2166,20 @@ const springBootActuator = "management:\n" +
"jolokia 通过HTTP暴露JMX beans(当Jolokia在类路径上时,WebFlux不可用) Yes\n" +
"logfile 返回日志文件内容(如果设置了logging.file或logging.path属性的话),支持使用HTTP Range头接收日志文件内容的部分信息 Yes\n" +
"prometheus 以可以被Prometheus服务器抓取的格式显示metrics信息 Yes";
-const springBootDruid = "druid:\n" +
- " ...\n" +
- " filters: stat,log4j # wall 这里关闭sql防火墙\n" +
- " stat-view-servlet:\n" +
- " enabled: true\n" +
- " url-pattern: /druid/*\n" +
- "# login-username: admin\n" +
- "# login-password: admin\n" +
- " reset-enable: false\n" +
- " # 防火墙配置\n" +
- "# wall:\n" +
- "# config:\n" +
- "# multi-statement-allow: false"
-
-const dirTraversal = '@GetMapping("/listdir")\n' +
- '@ResponseBody\n' +
- 'public String listDirectory(@RequestParam String dir) {\n' +
+const springBootDruid = "@Configuration\n" +
+ "public class DruidMonitorConfig {\n" +
+ " @Bean\n" +
+ " public ServletRegistrationBean druidStatViewServlet() {\n" +
+ " ServletRegistrationBean registrationBean =\n" +
+ " new ServletRegistrationBean<>(new StatViewServlet(), \"/druid/*\");\n" +
+ " registrationBean.addInitParameter(\"resetEnable\", \"false\");\n" +
+ " return registrationBean;\n" +
+ " }\n" +
+ "}\n" +
+ "\n" +
+ "// SecurityConfigurer 中放行 /druid/**,且未设置登录账号密码,会导致Druid监控台暴露。"
+
+const dirTraversal = 'public String listDirectory(String dir) {\n' +
' String staticFolderPath = sysConstant.getStaticFolder();\n' +
' File baseDir = new File(staticFolderPath);\n' +
' File requestedDir = new File(baseDir, dir);\n' +
@@ -1425,12 +2201,9 @@ const dirTraversal = '@GetMapping("/listdir")\n' +
' response.append(file.getName()).append("/\\">").append(file.getName()).append("/");\n' +
' ...\n' +
' return response.toString();\n' +
- '}';
+ '}'
-const safe1ListDirectory = '@GetMapping("/safe1listdir")\n' +
- '@ResponseBody\n' +
- '@SneakyThrows\n' +
- 'public String safe1ListDirectory(@RequestParam String dir) {\n' +
+const safe1ListDirectory = 'public String safe1(String dir) {\n' +
' String staticFolderPath = sysConstant.getStaticFolder();\n' +
' File baseDir = new File(staticFolderPath);\n' +
'\n' +
@@ -1442,26 +2215,26 @@ const safe1ListDirectory = '@GetMapping("/safe1listdir")\n' +
' }\n' +
' File requestedDir = new File(baseDir, dir);\n' +
' ...\n' +
- '}';
+ '}'
-const safe2ListDirectory = "@GetMapping(\"/safelistdir\")\n" +
- "@ResponseBody\n" +
- "public String safeListDirectory(@RequestParam String dir) {\n" +
- " String staticFolderPath = sysConstant.getStaticFolder();\n" +
- " File baseDir = new File(staticFolderPath);\n" +
- " File requestedDir = new File(baseDir, dir);\n" +
+const safe2ListDirectory = "public String safe2(String dir) {\n" +
+ " File baseDir = resolveStaticBaseDir();\n" +
+ " String relativeDir = normalizeRelativeDir(dir);\n" +
"\n" +
" // 检查请求的目录是否在规定目录内\n" +
"try {\n" +
- " if (!requestedDir.getCanonicalPath().startsWith(baseDir.getCanonicalPath()) || !requestedDir.isDirectory()) {\n" +
+ " Path basePath = baseDir.getCanonicalFile().toPath();\n" +
+ " File requestedDir = new File(baseDir, relativeDir);\n" +
+ " Path requestedPath = requestedDir.getCanonicalFile().toPath();\n" +
+ " if (!requestedPath.startsWith(basePath) || !requestedDir.isDirectory()) {\n" +
" return \"Directory not found or access denied.\";\n" +
" }\n" +
+ " return renderDirectoryListing(dir, requestedDir, true);\n" +
"} catch (IOException e) {\n" +
" return \"Error resolving directory path.\";\n" +
"}\n" +
"...";
-const infoLeakCeShi = "@GetMapping(\"/ping\")\n" +
- "public String ping(@RequestParam(name = \"ip\", required = false) String ip, Model model) {\n" +
+const infoLeakCeShi = "public String ping(String ip, Model model) {\n" +
" String result = \"\";\n" +
" if (ip != null && !ip.isEmpty()) {\n" +
" try {\n" +
@@ -1481,48 +2254,172 @@ const infoLeakCeShi = "@GetMapping(\"/ping\")\n" +
" } catch (Exception e) {\n" +
" result = \"Error: \" + e.getMessage();\n" +
" ...\n" +
- "}";
+ "}\n";
-// java专题 SPEL注入
-const spelVul = "public R spelVul(@ApiParam(name = \"ex\", value = \"表达式\", required = true) @RequestParam String ex) {\n" +
- " // 创建SpEL解析器,ExpressionParser接口用于表示解析器,SpelExpressionParser为默认实现\n" +
- " ExpressionParser parser = new SpelExpressionParser();\n" +
- " \n" +
- "// Expression expression = parser.parseExpression(ex);\n" +
- "// String result = expression.getValue().toString();\n" +
- " \n" +
- " // 构造上下文 上下文其实就是设置好某些变量的值,执行表达式时根据这些设置好的内容区获取值 在不配置的情况下具有默认类型的上下文\n" +
- " EvaluationContext evaluationContext = new StandardEvaluationContext();\n" +
- " \n" +
- " // 解析表达式,将用户输入的字符串解析为Expression对象\n" +
- " Expression exp = parser.parseExpression(ex);\n" +
- " \n" +
- " // 通过上下文计算表达式的值,并将结果转换为字符串\n" +
- " String result = exp.getValue(evaluationContext).toString();\n" +
- " return R.ok(result);\n" +
+// 登录对抗
+const vul1Account = "public class CustomUserDetailsService implements UserDetailsService {\n" +
+ " @Autowired\n" +
+ " private UserService userService;\n" +
+ "\t\n" +
+ " @Override\n" +
+ " public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {\n" +
+ " User sysUser = userService.getOne(Wrappers.query().lambda().eq(User::getUsername, username));\n" +
+ " if (ObjectUtil.isNull(sysUser)) {\n" +
+ " throw new UsernameNotFoundException(\"用户不存在\");\n" +
+ " // 安全写法:统一返回模糊错误信息\n" +
+ "\t\t// throw new UsernameNotFoundException(\"用户或密码错误\");\n" +
+ " }\n" +
+ "\n" +
+ " // 用户存在,直接返回 UserDetails 对象,不处理角色信息\n" +
+ " return new org.springframework.security.core.userdetails.User(sysUser.getUsername(), sysUser.getPassword(), new ArrayList<>());\n" +
+ " }\n" +
+ "}"
+const vul2Account = "public R vul2(String username, String password) {\n" +
+ "\t\n" +
+ "\t// 这里简单模拟下数据库查询操作\n" +
+ "\t// User user = UserService.getAllByUsernameAndPassword(username,password)\n" +
+ "\tif (\"admin\".equalsIgnoreCase(username) && \"admin\".equalsIgnoreCase(password)) {\n" +
+ "\t\treturn R.ok(\"登录成功!用户名:\" + username + \", 密码:\" + password);\n" +
+ "\t} else {\n" +
+ "\t\treturn R.ok(\"账号或密码错误!\");\n" +
+ "\t}\n" +
"}"
-const spelSafe = "public R spelSafe(@ApiParam(name = \"ex\", value = \"表达式\", required = true) @RequestParam String ex) {\n" +
- " ExpressionParser parser = new SpelExpressionParser();\n" +
- " \n" +
- "\t// 使用 SimpleEvaluationContext 限制表达式功能(Java类型引用、构造函数调用、Bean引用),防止危险的操作\n" +
- " EvaluationContext simpleContext = SimpleEvaluationContext.forReadOnlyDataBinding().build();\n" +
- " \n" +
- "\tExpression exp = parser.parseExpression(ex);\n" +
- " \n" +
- "\tString result = exp.getValue(simpleContext).toString();\n" +
- " return R.ok(result);\n" +
+const vul1Bypass = "$.ajax({\n" +
+ "type: 'POST',\n" +
+ "url: '/loginconfront/bypass/vul1step1',\n" +
+ "data: data.field,\n" +
+ "success: function (response) {\n" +
+ "\t// 步骤一账号校验通过后,模拟进行下一步校验\n" +
+ " if (response.code === 0) {\n" +
+ " $(\"#vul1-bypass-result\").text(response.msg);\n" +
+ " setTimeout(() => {\n" +
+ " $.ajax({\n" +
+ " type: 'POST',\n" +
+ " url: '/loginconfront/bypass/vul1step2',\n" +
+ " data: {code: response.code},\n" +
+ " ..."
+const vul2Bypass = "// step1:验证用户名并切换到步骤2\n" +
+ "$('#next1').on('click', function () {\n" +
+ " $.post('/loginconfront/bypass/step1', { username: username }, function (res) {\n" +
+ " if (res.code === 0) currentStep = 2;\n" +
+ " });\n" +
+ "});\n" +
+ "\n" +
+ "// step2:验证旧密码并切换到步骤3\n" +
+ "$('#next2').on('click', function () {\n" +
+ " $.post('/loginconfront/bypass/step2', { oldPassword: oldPassword }, function (res) {\n" +
+ " if (res.code === 0) currentStep = 3;\n" +
+ " });\n" +
+ "});\n" +
+ "\n" +
+ "// step3:提交新密码\n" +
+ "$('#reset-password-form').on('submit', function (e) {\n" +
+ " $.post('/loginconfront/bypass/step3', { newPassword: newPassword });\n" +
+ "});"
+const vul1Reverse = "// 与服务端密钥一致 用于生产签名Sign\n" +
+ "const key = \"FF38DC304A1D74B19F24A36C09FD6B72\";\n" +
+ "function generateSign(params) {\n" +
+ " const query = Object.keys(params)\n" +
+ " .sort()\n" +
+ " .map(k => `${k}=${params[k]}`)\n" +
+ " .join(\"&\");\n" +
+ " // 使用 MD5 加密生成签名\n" +
+ " return md5(query + key);\n" +
+ "}\n" +
+ "const params = {\n" +
+ " username: data.field.username,\n" +
+ " password: data.field.password,\n" +
+ " timestamp: Date.now(),\n" +
+ "};\n" +
+ "const sign = generateSign(params);\n" +
+ "\n" +
+ "{\"username\":\"admin\",\"password\":\"123456\",\"timestamp\":1732373468477,\"sign\":\"1a56f2b3de87c435be816341d9bcf6fe\"}"
+const vul2Reverse = "const publicKey = `-----BEGIN PUBLIC KEY-----\n" +
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx6Iq6tBvjNHqczQmJJAo\n" +
+ "otsBfKM/9yJCuTV87ZlI0Y1EFG65Jo89aLHW7BqBUJepcKC9kcA5PJaWSF5BYElt\n" +
+ "Y2NPnIfGkHamKeFywWh4aYy66MlBqr91Fw0Wyx8PQlp0CJKfiPEQmzwUobpimAvK\n" +
+ "...\n" +
+ "-----END PUBLIC KEY-----\n" +
+ "`;\n" +
+ "\n" +
+ "const encryptField = (field) => {\n" +
+ " const encryptor = new JSEncrypt();\n" +
+ " encryptor.setPublicKey(publicKey);\n" +
+ " return encryptor.encrypt(field);\n" +
+ "};\n" +
+ "\n" +
+ "const encryptedUsername = encryptField(data.field.username);\n" +
+ "const encryptedPassword = encryptField(data.field.password);\n" +
+ "\n" +
+ "{\"encryptedUsername\":\"iDF5BNv1zaM0V9qog0qzlUES3sCGYqmvrKiqPIvUgP5qE0pYn9XN3btW3PbRwLuySeruK2i8lem+L67w5+fFQBuRrpettLrHl8izIRp2W+nq9o9Kg/LSa3/+JynFoUHxrvQ2taNM1nustROpkBjJMbTOK52S6ZBa0quMw+wjfR1XExlzc99U1WJQfRAqj7Gsl9EPydRIh8vs4S/Nen5kf/dL3ZikfMbCUUBonRlYy6a3nWJ412P+hxRbSl80Z8aQKw9lH4+Iju80oFmQ6DuS6Ce70h88z/Va+xzXHDzM8w6h5iqQLzq3Kj/E+b/wsn6eM7v+LEC8LwLQ/t8z8tki9g==\",\n" +
+ "\"encryptedPassword\":\"nDI0/PBwsFHnRRw7Z4gHZ6G8Uaq7BUjUxnTDw7bkR9nrTkoHfcDLKUddj2JS7WWbOyuwsUFce3/tXJYQWNMFQqGRtf6jXxFAlvTvBkRdsZXOIU+Abb4EqYw670xd5UTeAQ0lI5KNXtw6e/VbnXyX+STJdN2SO7FLbvZ4sM6gLQSVWLo/+pZsYxKlEUNxew2svlzDZtqKnyF12bzakWfzaWuovLnYCCEXV1oAJCErjgfoOS2wJADdgU0wE6KlFDMNjsCvONmO6KZpmJQ1GOq3MpyqySq8eyJkYG3cDSRo5nDo2YOcevOHifzMnKbrU9gh4/RUj8sxrykdqgLmzX3rhw==\"}"
+
+const vul1Credential = "public R generateJWT(String username, String role) {\n" +
+ " String jwt = Jwts.builder()\n" +
+ " .setSubject(username)\n" +
+ " .claim(\"role\", role)\n" +
+ " .signWith(jwtKey())\n" +
+ " .compact();\n" +
+ " return R.ok(jwt);\n" +
+ "}\n" +
+ "\n" +
+ "public R vul1(String jwt) {\n" +
+ " String user = Jwts.parser()\n" +
+ " .setSigningKey(jwtKey())\n" +
+ " .parseClaimsJws(jwt)\n" +
+ " .getBody()\n" +
+ " .getSubject();\n" +
+ " String role = Jwts.parserBuilder()\n" +
+ " .setSigningKey(jwtKey())\n" +
+ " .build()\n" +
+ " .parseClaimsJws(jwt)\n" +
+ " .getBody()\n" +
+ " .get(\"role\", String.class);\n" +
+ " return R.ok(\"JWT解析成功,user:\" + user + \",role:\" + role);\n" +
+ "}"
+
+const vul2Credential = "vul2Credential"
+
+
+// java专题 SPEL注入
+const spelVul = "public R vul(String ex) {\n" +
+ " try {\n" +
+ " ExpressionParser parser = new SpelExpressionParser();\n" +
+ " EvaluationContext evaluationContext = new StandardEvaluationContext();\n" +
+ " Expression exp = parser.parseExpression(ex);\n" +
+ " Object result = exp.getValue(evaluationContext);\n" +
+ " return R.ok(String.valueOf(result));\n" +
+ " } catch (Exception e) {\n" +
+ " return R.error(\"SPEL表达式执行失败:\" + e.getMessage());\n" +
+ " }\n" +
"}"
-const sstiVul = "public String sstiVul(@ApiParam(name = \"para\", value = \"用户输入参数\", required = true) @RequestParam String para, Model model) {\n" +
- " // 用户输入直接拼接到模板路径,可能导致SSTI(服务器端模板注入)漏洞\n" +
- " return \"/vul/ssti/\" + para;\n" +
+const spelSafe = "public R safe(String ex) {\n" +
+ " try {\n" +
+ " ExpressionParser parser = new SpelExpressionParser();\n" +
+ " // 使用 SimpleEvaluationContext 限制 Java 类型引用、构造函数调用、Bean 引用等危险能力\n" +
+ " EvaluationContext simpleContext = SimpleEvaluationContext.forReadOnlyDataBinding().build();\n" +
+ " Expression exp = parser.parseExpression(ex);\n" +
+ " Object result = exp.getValue(simpleContext);\n" +
+ " return R.ok(String.valueOf(result));\n" +
+ " } catch (Exception e) {\n" +
+ " return R.error(\"表达式被安全上下文限制:\" + e.getMessage());\n" +
+ " }\n" +
+ "}\n"
+
+const sstiVul = "public String vul1(@RequestParam String para, Model model) {\n" +
+ " // 用户输入直接拼接到模板路径,Thymeleaf 会对视图名中的 __${...}__ 做预处理\n" +
+ " return \"vul/ssti/\" + para;\n" +
"}\n" +
"\n" +
- "public void sstiVul2(@PathVariable String path) {\n" +
+ "public String vul2(@PathVariable String path) {\n" +
+ " // URL 路径变量直接拼接到模板路径,同样会触发 Thymeleaf 视图名预处理\n" +
" log.info(\"SSTI注入:\"+path);\n" +
+ " return \"vul/ssti/\" + path;\n" +
"}\n" +
"\n" +
+ "// 缺陷组件版本参考\n" +
"\n" +
" org.springframework.boot \n" +
" spring-boot-starter-parent \n" +
@@ -1536,67 +2433,57 @@ const sstiVul = "public String sstiVul(@ApiParam(name = \"para\", value = \"用
" spring-boot-starter-thymeleaf \n" +
" 2.4.1 \n" +
"\n"
-const sstiSafe = "@GetMapping(\"/safe-thymeleaf\")\n" +
- "public String sstiSafe(@ApiParam(name = \"para\", value = \"用户输入参数\", required = true) @RequestParam String para, Model model) {\n" +
+const sstiSafe = "public String safe1(String para, Model model) {\n" +
" List white_list = new ArrayList<>(Arrays.asList(\"vul\", \"ssti\"));\n" +
" if (white_list.contains(para)){\n" +
- " return \"vul/ssti\" + para;\n" +
+ " return \"vul/ssti/\" + para;\n" +
" } else{\n" +
" return \"common/401\";\n" +
" }\n" +
"}\n" +
"@GetMapping(\"/safe2/{path}\")\n" +
- "public void sstiSafe2(@PathVariable String path, HttpServletResponse response) {\n" +
+ "public void safe2(@PathVariable String path, HttpServletResponse response) throws IOException {\n" +
" log.info(\"SSTI注入:\"+path);\n" +
+ " response.setContentType(\"text/plain;charset=UTF-8\");\n" +
+ " response.getWriter().write(\"已跳过视图解析,输入路径:\" + path);\n" +
"}"
-const vulReadObject = "@RequestMapping(\"/vulReadObject\")\n" +
- "@ResponseBody\n" +
- "public R vulReadObject(String payload) {\n" +
+const vulReadObject = "public R vul(String payload) {\n" +
" try {\n" +
- " payload = payload.replace(\" \", \"+\");\n" +
- " byte[] bytes = Base64.getDecoder().decode(payload);\n" +
- " ByteArrayInputStream stream = new ByteArrayInputStream(bytes);\n" +
- " java.io.ObjectInputStream in = new java.io.ObjectInputStream(stream);\n" +
- " in.readObject();\n" +
- " in.close();\n" +
- " return R.ok(\"[+]Java反序列化:ObjectInputStream.readObject()\");\n" +
+ " byte[] bytes = decodePayload(payload);\n" +
+ " Object obj;\n" +
+ " try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes))) {\n" +
+ " obj = in.readObject();\n" +
+ " }\n" +
+ " return R.ok(\"[+]Java反序列化:\" + obj);\n" +
" } catch (Exception e) {\n" +
" return R.error(\"[-]请输入正确的Payload!\\n\"+e.getMessage());\n" +
" }\n" +
- "}"
-const safeReadObject1 = "@RequestMapping(\"/safeReadObject1\")\n" +
- "@ResponseBody\n" +
- "public R safeReadObject1(String payload) {\n" +
- " // 安全措施:禁用不安全的反序列化\n" +
+"}"
+const safeReadObject1 = "public R safe1(String payload) {\n" +
+ " // 禁用 Commons Collections 不安全反序列化开关\n" +
" System.setProperty(\"org.apache.commons.collections.enableUnsafeSerialization\", \"false\");\n" +
" try {\n" +
- " payload = payload.replace(\" \", \"+\");\n" +
- " byte[] bytes = Base64.getDecoder().decode(payload);\n" +
- " ByteArrayInputStream stream = new ByteArrayInputStream(bytes);\n" +
- " java.io.ObjectInputStream in = new java.io.ObjectInputStream(stream);\n" +
- " in.readObject();\n" +
- " in.close();\n" +
- " return R.ok(\"[+]Java反序列化:ObjectInputStream.readObject()\");\n" +
+ " byte[] bytes = decodePayload(payload);\n" +
+ " try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes))) {\n" +
+ " in.readObject();\n" +
+ " }\n" +
+ " return R.ok(\"[+]Java反序列化:禁用Commons Collections不安全反序列化开关\");\n" +
" } catch (Exception e) {\n" +
" return R.error(\"[-]请输入正确的Payload!\\n\"+e.getMessage());\n" +
" }\n" +
"}"
-const safeReadObject2 = "@RequestMapping(\"/safeReadObject2\")\n" +
- "@ResponseBody\n" +
- "public R safeReadObject2(String payload) {\n" +
+const safeReadObject2 = "public R safe2(String payload) {\n" +
" try {\n" +
- " payload = payload.replace(\" \", \"+\");\n" +
- " byte[] bytes = Base64.getDecoder().decode(payload);\n" +
- " ByteArrayInputStream stream = new ByteArrayInputStream(bytes);\n" +
+ " byte[] bytes = decodePayload(payload);\n" +
" // 创建 ValidatingObjectInputStream 对象\n" +
- " ValidatingObjectInputStream ois = new ValidatingObjectInputStream(stream);\n" +
- " // 设置拒绝反序列化的类\n" +
- " ois.reject(java.lang.Runtime.class);\n" +
- " ois.reject(java.lang.ProcessBuilder.class);\n" +
- " // 只允许反序列化Sqli类\n" +
- " ois.accept(Sqli.class);\n" +
- " ois.readObject();\n" +
+ " try (ValidatingObjectInputStream ois = new ValidatingObjectInputStream(new ByteArrayInputStream(bytes))) {\n" +
+ " ois.reject(java.lang.Runtime.class);\n" +
+ " ois.reject(java.lang.ProcessBuilder.class);\n" +
+ " // 只允许反序列化Sqli类\n" +
+ " ois.accept(Sqli.class);\n" +
+ " ois.readObject();\n" +
+ " }\n" +
" return R.ok(\"[+]Java反序列化:ObjectInputStream.readObject()\");\n" +
" } catch (Exception e) {\n" +
" return R.error(\"[-]请输入正确的Payload!\\n\"+e.getMessage());\n" +
@@ -1604,30 +2491,28 @@ const safeReadObject2 = "@RequestMapping(\"/safeReadObject2\")\n" +
"}"
const safeReadObject3 = "safeReadObject3"
-const vulSnakeYaml = "@PostMapping(\"/vulSnakeYaml\")\n" +
- "@ResponseBody\n" +
- "public R vulSnakeYaml(String payload) {\n" +
+const vulSnakeYaml = "public R vul(String payload) {\n" +
+ " if (payload == null || payload.trim().isEmpty()) {\n" +
+ " return R.error(\"Payload不能为空\");\n" +
+ " }\n" +
" Yaml y = new Yaml();\n" +
- " y.load(payload);\n" +
- " return R.ok(\"[+]Java反序列化:SnakeYaml\");\n" +
+ " Object result = y.load(payload);\n" +
+ " return R.ok(\"[+]Java反序列化:SnakeYaml原生漏洞,解析结果:\" + result);\n" +
"}\n" +
"\n" +
- "payload示例:\n" +
- "payload=!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ['http://127.0.0.1:7777/yaml-payload.jar']]]]"
-const safeSnakeYaml = "@PostMapping(\"/safeSnakeYaml\")\n" +
- "public R safeSnakeYaml(String payload) {\n" +
+ "// payload示例\n" +
+ "payload=!!top.whgojp.modules.sqli.entity.Sqli {id: 1, username: test, password: pass}\n"
+const safeSnakeYaml = "public R safe(String payload) {\n" +
" try {\n" +
" Yaml y = new Yaml(new SafeConstructor());\n" +
- " y.load(payload);\n" +
- " return R.ok(\"[-]Java反序列化:SnakeYaml安全构造\");\n" +
+ " Object result = y.load(payload);\n" +
+ " return R.ok(\"[+]Java反序列化:SnakeYaml安全构造,解析结果:\" + result);\n" +
" } catch (Exception e) {\n" +
- " return R.error(\"[-]Java反序列化:SnakeYaml反序列化失败\");\n" +
+ " return R.error(\"[-]Java反序列化:SnakeYaml反序列化失败:\" + e.getMessage());\n" +
" }\n" +
- "}"
+"}"
-
-const vulXmlDecoder = '@RequestMapping("/vulXmlDecoder")\n' +
- 'public R vulXmlDecoder(String payload) {\n' +
+const vulXmlDecoder = 'public R vul(String payload) {\n' +
' String[] strCmd = payload.split(" ");\n' +
' StringBuilder xml = new StringBuilder()\n' +
' .append("")\n' +
@@ -1648,11 +2533,28 @@ const vulXmlDecoder = '@RequestMapping("/vulXmlDecoder")\n' +
' }\n' +
'}'
-const safeXmlDecoder = "vulXmlDecoder"
+const safeXmlDecoder = 'public R safe(@RequestParam String payload) {\n' +
+ ' try {\n' +
+ ' // 构建 XML 字符串\n' +
+ ' ...\n' +
+ ' // 使用 SAX 解析器解析 XML\n' +
+ ' SAXParserFactory factory = SAXParserFactory.newInstance();\n' +
+ ' SAXParser saxParser = factory.newSAXParser();\n' +
+ ' CommandHandler handler = new CommandHandler();\n' +
+ ' // 将 ByteArrayInputStream 包装成 InputSource\n' +
+ ' InputSource inputSource = new InputSource(new ByteArrayInputStream(xml.toString().getBytes(StandardCharsets.UTF_8)));\n' +
+ ' saxParser.parse(inputSource, handler);\n' +
+ ' // 获取解析后的命令参数\n' +
+ ' List args = handler.getArgs();\n' +
+ ' // 处理解析后的命令参数\n' +
+ ' System.out.println("Parsed command: " + String.join(" ", args));\n' +
+ ' return R.ok("[+]命令解析成功:"+String.join(" ", args));\n' +
+ ' } catch (Exception e) {\n' +
+ ' return R.error("[-]命令解析失败: " + e.getMessage());\n' +
+ ' }\n' +
+ '}'
-const vulFastjson = "@PostMapping(\"/vul\")\n" +
- "@ResponseBody\n" +
- "public String vulFastjson(@RequestBody String content) {\n" +
+const vulFastjson = "public String vul(@RequestBody String content) {\n" +
" try {\n" +
" JSONObject jsonObject = JSON.parseObject(content);\n" +
" return jsonObject.toString();\n" +
@@ -1666,16 +2568,14 @@ const vulFastjson = "@PostMapping(\"/vul\")\n" +
" fastjson \n" +
" 1.2.37 \n" +
""
-const safeFastjson = "@PostMapping(\"/safe\")\n" +
- "@ResponseBody\n" +
- "public String safeFastjson(@RequestBody String content) {\n" +
+const safeFastjson = "public String safe(@RequestBody String content) {\n" +
" try {\n" +
" // 1、禁用 AutoType\n" +
" ParserConfig.getGlobalInstance().setAutoTypeSupport(false);\n" +
" // 2、使用AutoType白名单机制\n" +
"// ParserConfig.getGlobalInstance().setAutoTypeSupport(true);\n" +
"// ParserConfig.getGlobalInstance().addAccept(\"top.whgojp.WhiteListClass\");\n" +
- " // 3、1.2.68之后的版本,Fastjson真家里safeMode的支持\n" +
+ " // 3、1.2.68之后的版本,Fastjson增加了safeMode的支持\n" +
"// ParserConfig.getGlobalInstance().setSafeMode(true);\n" +
"// JSONObject jsonObject = JSON.parseObject(content, Feature.DisableSpecialKeyDetect);\n" +
" JSONObject jsonObject = JSON.parseObject(content);\n" +
@@ -1690,18 +2590,49 @@ const safeFastjson = "@PostMapping(\"/safe\")\n" +
" 1.2.83版本以上 \n" +
""
-const vulXstream = "@RequestMapping(\"/vul\")\n" +
- "@ResponseBody\n" +
- "public String vulXstream(@RequestBody String content) {\n" +
- "\tXStream xs = new XStream();\n" +
- "\tObject result = xs.fromXML(content); // 反序列化得到的对象\n" +
+const vulJackson = "public String vul(@RequestBody String content) {\n" +
+ " try {\n" +
+ " ObjectMapper mapper = new ObjectMapper();\n" +
+ " mapper.enableDefaultTyping(); // 启用多态类型处理\n" +
+ "\n" +
+ " // 反序列化接收的JSON数据,触发漏洞\n" +
+ " Object obj = mapper.readValue(content, Object.class);\n" +
+ " return \"[+]Jackson 反序列化: \" + obj.toString();\n" +
+ " } catch (Exception e) {\n" +
+ " e.printStackTrace();\n" +
+ " return \"[-]Jackson反序列化失败\";\n" +
+ " }\n" +
+ "}"
+
+const safeJackson = "public String safe(@RequestBody String payload) {\n" +
+ " try {\n" +
+ " ObjectMapper mapper = new ObjectMapper();\n" +
"\n" +
- "\t// 检查反序列化后的结果并返回相关信息\n" +
- "\treturn \"组件漏洞-Xstream Vul, 反序列化结果: \\n\" + result.toString();\n" +
+ " // 启用安全的类型验证\n" +
+ " mapper.activateDefaultTyping(\n" +
+ " LaissezFaireSubTypeValidator.instance,\n" +
+ " ObjectMapper.DefaultTyping.NON_FINAL\n" +
+ " );\n" +
+ " mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);\n" +
+ "\n" +
+ " // 反序列化传入的JSON数据\n" +
+ " Map safePayload = mapper.readValue(payload, Map.class);\n" +
+ " return mapper.writeValueAsString(safePayload);\n" +
+ " } catch (Exception e) {\n" +
+ " e.printStackTrace();\n" +
+ " return \"Jackson Safe Deserialization Error\";\n" +
+ " }\n" +
"}"
-const safeXstreamBlackList = "@RequestMapping(\"/safe-BlackList\")\n" +
- "public String safeXstreamBlackList(@RequestBody String content) {\n" +
+const vulXstream = "public String vul(@RequestBody String content) {\n" +
+ " XStream xs = new XStream();\n" +
+ " Object result = xs.fromXML(content); // 反序列化得到的对象\n" +
+ "\n" +
+ " // 检查反序列化后的结果并返回相关信息\n" +
+ " return \"组件漏洞-Xstream Vul, 反序列化结果: \\n\" + result.toString();\n" +
+ "}"
+
+const safeXstreamBlackList = "public String safe1(@RequestBody String content) {\n" +
" XStream xstream = new XStream();\n" +
" // 首先清除默认设置,然后进行自定义设置\n" +
" xstream.addPermission(NoTypePermission.NONE);\n" +
@@ -1711,8 +2642,7 @@ const safeXstreamBlackList = "@RequestMapping(\"/safe-BlackList\")\n" +
" return \"组件漏洞-Xstream Safe-BlackList\";\n" +
"}"
-const safeXstreamWhiteList = "@RequestMapping(\"/safe-WhiteList\")\n" +
- "public String safeXstreamWhiteList(@RequestBody String content) {\n" +
+const safeXstreamWhiteList = "public String safe2(@RequestBody String content) {\n" +
" XStream xstream = new XStream();\n" +
" // 首先清除默认设置,然后进行自定义设置\n" +
" xstream.addPermission(NoTypePermission.NONE);\n" +
@@ -1723,19 +2653,28 @@ const safeXstreamWhiteList = "@RequestMapping(\"/safe-WhiteList\")\n" +
" // 添加自定义的类列表\n" +
" xstream.addPermission(new ExplicitTypePermission(new Class[]{Date.class}));\n" +
" return \"组件漏洞-Xstream Safe-WhiteList\";\n" +
- "}\n"
-
-const vulLog4j2 = "@PostMapping(\"/vul\")\n" +
- "@ResponseBody\n" +
- "public String vulLog4j2(@RequestParam(\"payload\") String payload) {\n" +
- "\tlogger.error(payload);\t//此处解析${}从而触发漏洞\n" +
- "\treturn \"[+]Log4j2反序列化:\"+payload;\n" +
"}"
+
+const vulLog4j2 = "public String vul(String payload) {\n" +
+ " //此处解析${}从而触发漏洞\n" +
+ " logger.error(payload); \n" +
+ " return \"[+]Log4j2反序列化:\"+payload;\n" +
+ "}\n" +
+ "\n" +
+ "\n" +
+ " org.apache.logging.log4j \n" +
+ " log4j-core \n" +
+ " 2.8.2 \n" +
+ " \n" +
+ "\n" +
+ "\n" +
+ " org.apache.logging.log4j \n" +
+ " log4j-api \n" +
+ " 2.8.2 \n" +
+ " "
const safeLog4j2 = "safeLog4j2"
-const vulShiro = "@GetMapping(\"/getAESKey\")\n" +
- "@ResponseBody\n" +
- "public R getShiroKey(){\n" +
+const vulShiro = "public R getShiroKey(){\n" +
" try{\n" +
" byte[] key = new CookieRememberMeManager().getCipherKey();\n" +
" return R.ok(\"Shiro AES密钥硬编码为:\"+new String(Base64.getEncoder().encode(key)));\n" +
@@ -1749,3 +2688,24 @@ const vulShiro = "@GetMapping(\"/getAESKey\")\n" +
" shiro-spring \n" +
" 1.2.4 \n" +
""
+const JdbcDeserial = "public R jdbc() {\n" +
+ " try (Connection conn = DriverManager.getConnection(url, username, password);\n" +
+ " Statement stmt = conn.createStatement()) {\n" +
+ " ResultSet rs = stmt.executeQuery(\"SELECT malicious_object FROM objects WHERE id = 1\");\n" +
+ " if (rs.next()) {\n" +
+ " byte[] bytes = rs.getBytes(\"malicious_object\");\n" +
+ " try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {\n" +
+ " // 触发反序列化漏洞\n" +
+ " ois.readObject();\n" +
+ " }\n" +
+ " }\n" +
+ " return R.ok(\"触发MYSQL-JDBC反序列化漏洞!\");\n" +
+ " } catch (Exception e) {\n" +
+ " return R.error(\"触发MYSQL-JDBC反序列化漏洞失败:\" + e.getMessage());\n" +
+ " }\n" +
+ "}\n" +
+ "\n" +
+ "private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {\n" +
+ " in.defaultReadObject();\n" +
+ " Runtime.getRuntime().exec(command);\n" +
+ "}"
diff --git a/src/main/resources/static/lib/custom-icon/demo.css b/src/main/resources/static/lib/custom-icon/demo.css
old mode 100644
new mode 100755
diff --git a/src/main/resources/static/lib/custom-icon/demo_index.html b/src/main/resources/static/lib/custom-icon/demo_index.html
old mode 100644
new mode 100755
index 12b1893..d51334c
--- a/src/main/resources/static/lib/custom-icon/demo_index.html
+++ b/src/main/resources/static/lib/custom-icon/demo_index.html
@@ -54,6 +54,120 @@
第二步:定义使用 iconfont 的样式
@@ -410,6 +524,177 @@ 第三步:挑选相应图标并获取字体编码,应用于页面