js演练场

This commit is contained in:
q
2025-11-29 03:41:51 +08:00
parent df646b8c43
commit b4b1d7f923
25 changed files with 6379 additions and 112 deletions

View File

@@ -0,0 +1,436 @@
package cn.qaiu.lz.web.controller;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.lz.web.model.PlaygroundTestResp;
import cn.qaiu.lz.web.service.DbService;
import cn.qaiu.parser.ParserCreate;
import cn.qaiu.parser.customjs.JsPlaygroundExecutor;
import cn.qaiu.parser.customjs.JsPlaygroundLogger;
import cn.qaiu.parser.customjs.JsScriptMetadataParser;
import cn.qaiu.vx.core.annotaions.RouteHandler;
import cn.qaiu.vx.core.annotaions.RouteMapping;
import cn.qaiu.vx.core.enums.RouteMethod;
import cn.qaiu.vx.core.model.JsonResult;
import cn.qaiu.vx.core.util.AsyncServiceUtil;
import cn.qaiu.vx.core.util.ResponseUtil;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 演练场API控制器
* 提供JavaScript解析脚本的测试接口
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
@RouteHandler(value = "/v2/playground", order = 10)
@Slf4j
public class PlaygroundApi {
private static final int MAX_PARSER_COUNT = 100;
private final DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
/**
* 测试执行JavaScript代码
*
* @param ctx 路由上下文
* @return 测试结果
*/
@RouteMapping(value = "/test", method = RouteMethod.POST)
public Future<JsonObject> test(RoutingContext ctx) {
Promise<JsonObject> promise = Promise.promise();
try {
JsonObject body = ctx.body().asJsonObject();
String jsCode = body.getString("jsCode");
String shareUrl = body.getString("shareUrl");
String pwd = body.getString("pwd");
String method = body.getString("method", "parse");
// 参数验证
if (StringUtils.isBlank(jsCode)) {
promise.complete(JsonObject.mapFrom(PlaygroundTestResp.builder()
.success(false)
.error("JavaScript代码不能为空")
.build()));
return promise.future();
}
if (StringUtils.isBlank(shareUrl)) {
promise.complete(JsonObject.mapFrom(PlaygroundTestResp.builder()
.success(false)
.error("分享链接不能为空")
.build()));
return promise.future();
}
// 验证方法类型
if (!"parse".equals(method) && !"parseFileList".equals(method) && !"parseById".equals(method)) {
promise.complete(JsonObject.mapFrom(PlaygroundTestResp.builder()
.success(false)
.error("方法类型无效,必须是 parse、parseFileList 或 parseById")
.build()));
return promise.future();
}
long startTime = System.currentTimeMillis();
try {
// 创建ShareLinkInfo
ParserCreate parserCreate = ParserCreate.fromShareUrl(shareUrl);
if (StringUtils.isNotBlank(pwd)) {
parserCreate.setShareLinkInfoPwd(pwd);
}
ShareLinkInfo shareLinkInfo = parserCreate.getShareLinkInfo();
// 创建演练场执行器
JsPlaygroundExecutor executor = new JsPlaygroundExecutor(shareLinkInfo, jsCode);
// 根据方法类型选择执行,并异步处理结果
Future<Object> executionFuture;
switch (method) {
case "parse":
executionFuture = executor.executeParseAsync().map(r -> (Object) r);
break;
case "parseFileList":
executionFuture = executor.executeParseFileListAsync().map(r -> (Object) r);
break;
case "parseById":
executionFuture = executor.executeParseByIdAsync().map(r -> (Object) r);
break;
default:
promise.fail(new IllegalArgumentException("未知的方法类型: " + method));
return promise.future();
}
// 异步处理执行结果
executionFuture.onSuccess(result -> {
log.debug("执行成功,结果类型: {}, 结果值: {}",
result != null ? result.getClass().getSimpleName() : "null",
result);
// 获取日志
List<JsPlaygroundLogger.LogEntry> logEntries = executor.getLogs();
log.debug("获取到 {} 条日志记录", logEntries.size());
List<PlaygroundTestResp.LogEntry> respLogs = logEntries.stream()
.map(entry -> PlaygroundTestResp.LogEntry.builder()
.level(entry.getLevel())
.message(entry.getMessage())
.timestamp(entry.getTimestamp())
.source(entry.getSource()) // 使用日志条目的来源标识
.build())
.collect(Collectors.toList());
long executionTime = System.currentTimeMillis() - startTime;
// 构建响应
PlaygroundTestResp response = PlaygroundTestResp.builder()
.success(true)
.result(result)
.logs(respLogs)
.executionTime(executionTime)
.build();
JsonObject jsonResponse = JsonObject.mapFrom(response);
log.debug("测试成功响应: {}", jsonResponse.encodePrettily());
promise.complete(jsonResponse);
}).onFailure(e -> {
long executionTime = System.currentTimeMillis() - startTime;
String errorMessage = e.getMessage();
String stackTrace = getStackTrace(e);
log.error("演练场执行失败", e);
// 尝试获取已有的日志
List<JsPlaygroundLogger.LogEntry> logEntries = executor.getLogs();
List<PlaygroundTestResp.LogEntry> respLogs = logEntries.stream()
.map(entry -> PlaygroundTestResp.LogEntry.builder()
.level(entry.getLevel())
.message(entry.getMessage())
.timestamp(entry.getTimestamp())
.source(entry.getSource()) // 使用日志条目的来源标识
.build())
.collect(Collectors.toList());
PlaygroundTestResp response = PlaygroundTestResp.builder()
.success(false)
.error(errorMessage)
.stackTrace(stackTrace)
.executionTime(executionTime)
.logs(respLogs)
.build();
promise.complete(JsonObject.mapFrom(response));
});
} catch (Exception e) {
long executionTime = System.currentTimeMillis() - startTime;
String errorMessage = e.getMessage();
String stackTrace = getStackTrace(e);
log.error("演练场初始化失败", e);
PlaygroundTestResp response = PlaygroundTestResp.builder()
.success(false)
.error(errorMessage)
.stackTrace(stackTrace)
.executionTime(executionTime)
.logs(new ArrayList<>())
.build();
promise.complete(JsonObject.mapFrom(response));
}
} catch (Exception e) {
log.error("解析请求参数失败", e);
promise.complete(JsonObject.mapFrom(PlaygroundTestResp.builder()
.success(false)
.error("解析请求参数失败: " + e.getMessage())
.stackTrace(getStackTrace(e))
.build()));
}
return promise.future();
}
/**
* 获取types.js文件内容
*
* @param response HTTP响应
*/
@RouteMapping(value = "/types.js", method = RouteMethod.GET)
public void getTypesJs(HttpServerResponse response) {
try (InputStream inputStream = getClass().getClassLoader()
.getResourceAsStream("custom-parsers/types.js")) {
if (inputStream == null) {
ResponseUtil.fireJsonResultResponse(response, JsonResult.error("types.js文件不存在"));
return;
}
String content = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))
.lines()
.collect(Collectors.joining("\n"));
response.putHeader("Content-Type", "text/javascript; charset=utf-8")
.end(content);
} catch (Exception e) {
log.error("读取types.js失败", e);
ResponseUtil.fireJsonResultResponse(response, JsonResult.error("读取types.js失败: " + e.getMessage()));
}
}
/**
* 获取解析器列表
*/
@RouteMapping(value = "/parsers", method = RouteMethod.GET)
public Future<JsonObject> getParserList() {
return dbService.getPlaygroundParserList();
}
/**
* 保存解析器
*/
@RouteMapping(value = "/parsers", method = RouteMethod.POST)
public Future<JsonObject> saveParser(RoutingContext ctx) {
Promise<JsonObject> promise = Promise.promise();
try {
JsonObject body = ctx.body().asJsonObject();
String jsCode = body.getString("jsCode");
if (StringUtils.isBlank(jsCode)) {
promise.complete(JsonResult.error("JavaScript代码不能为空").toJsonObject());
return promise.future();
}
// 解析元数据
try {
var config = JsScriptMetadataParser.parseScript(jsCode);
String type = config.getType();
String displayName = config.getDisplayName();
String name = config.getMetadata().get("name");
String description = config.getMetadata().get("description");
String author = config.getMetadata().get("author");
String version = config.getMetadata().get("version");
String matchPattern = config.getMatchPattern() != null ? config.getMatchPattern().pattern() : null;
// 检查数量限制
dbService.getPlaygroundParserCount().onSuccess(count -> {
if (count >= MAX_PARSER_COUNT) {
promise.complete(JsonResult.error("解析器数量已达到上限(" + MAX_PARSER_COUNT + "个),请先删除不需要的解析器").toJsonObject());
return;
}
// 检查type是否已存在
dbService.getPlaygroundParserList().onSuccess(listResult -> {
var list = listResult.getJsonArray("data");
boolean exists = false;
if (list != null) {
for (int i = 0; i < list.size(); i++) {
var item = list.getJsonObject(i);
if (type.equals(item.getString("type"))) {
exists = true;
break;
}
}
}
if (exists) {
promise.complete(JsonResult.error("解析器类型 " + type + " 已存在,请使用其他类型标识").toJsonObject());
return;
}
// 保存到数据库
JsonObject parser = new JsonObject();
parser.put("name", name);
parser.put("type", type);
parser.put("displayName", displayName);
parser.put("description", description);
parser.put("author", author);
parser.put("version", version);
parser.put("matchPattern", matchPattern);
parser.put("jsCode", jsCode);
parser.put("ip", getClientIp(ctx.request()));
parser.put("enabled", true);
dbService.savePlaygroundParser(parser).onSuccess(result -> {
promise.complete(result);
}).onFailure(e -> {
log.error("保存解析器失败", e);
promise.complete(JsonResult.error("保存失败: " + e.getMessage()).toJsonObject());
});
}).onFailure(e -> {
log.error("获取解析器列表失败", e);
promise.complete(JsonResult.error("检查解析器失败: " + e.getMessage()).toJsonObject());
});
}).onFailure(e -> {
log.error("获取解析器数量失败", e);
promise.complete(JsonResult.error("检查解析器数量失败: " + e.getMessage()).toJsonObject());
});
} catch (Exception e) {
log.error("解析脚本元数据失败", e);
promise.complete(JsonResult.error("解析脚本元数据失败: " + e.getMessage()).toJsonObject());
}
} catch (Exception e) {
log.error("解析请求参数失败", e);
promise.complete(JsonResult.error("解析请求参数失败: " + e.getMessage()).toJsonObject());
}
return promise.future();
}
/**
* 更新解析器
*/
@RouteMapping(value = "/parsers/:id", method = RouteMethod.PUT)
public Future<JsonObject> updateParser(RoutingContext ctx, Long id) {
Promise<JsonObject> promise = Promise.promise();
try {
JsonObject body = ctx.body().asJsonObject();
String jsCode = body.getString("jsCode");
if (StringUtils.isBlank(jsCode)) {
promise.complete(JsonResult.error("JavaScript代码不能为空").toJsonObject());
return promise.future();
}
// 解析元数据
try {
var config = JsScriptMetadataParser.parseScript(jsCode);
String displayName = config.getDisplayName();
String name = config.getMetadata().get("name");
String description = config.getMetadata().get("description");
String author = config.getMetadata().get("author");
String version = config.getMetadata().get("version");
String matchPattern = config.getMatchPattern() != null ? config.getMatchPattern().pattern() : null;
JsonObject parser = new JsonObject();
parser.put("name", name);
parser.put("displayName", displayName);
parser.put("description", description);
parser.put("author", author);
parser.put("version", version);
parser.put("matchPattern", matchPattern);
parser.put("jsCode", jsCode);
parser.put("enabled", body.getBoolean("enabled", true));
dbService.updatePlaygroundParser(id, parser).onSuccess(result -> {
promise.complete(result);
}).onFailure(e -> {
log.error("更新解析器失败", e);
promise.complete(JsonResult.error("更新失败: " + e.getMessage()).toJsonObject());
});
} catch (Exception e) {
log.error("解析脚本元数据失败", e);
promise.complete(JsonResult.error("解析脚本元数据失败: " + e.getMessage()).toJsonObject());
}
} catch (Exception e) {
log.error("解析请求参数失败", e);
promise.complete(JsonResult.error("解析请求参数失败: " + e.getMessage()).toJsonObject());
}
return promise.future();
}
/**
* 删除解析器
*/
@RouteMapping(value = "/parsers/:id", method = RouteMethod.DELETE)
public Future<JsonObject> deleteParser(Long id) {
return dbService.deletePlaygroundParser(id);
}
/**
* 根据ID获取解析器
*/
@RouteMapping(value = "/parsers/:id", method = RouteMethod.GET)
public Future<JsonObject> getParserById(Long id) {
return dbService.getPlaygroundParserById(id);
}
/**
* 获取客户端IP
*/
private String getClientIp(HttpServerRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.remoteAddress().host();
}
return ip;
}
/**
* 获取异常堆栈信息
*/
private String getStackTrace(Throwable throwable) {
if (throwable == null) {
return "";
}
java.io.StringWriter sw = new java.io.StringWriter();
java.io.PrintWriter pw = new java.io.PrintWriter(sw);
throwable.printStackTrace(pw);
return sw.toString();
}
}

View File

@@ -0,0 +1,62 @@
package cn.qaiu.lz.web.model;
import cn.qaiu.db.ddl.Constraint;
import cn.qaiu.db.ddl.Length;
import cn.qaiu.db.ddl.Table;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* 演练场解析器实体
* 用于保存用户创建的临时JS解析器
*/
@Data
@Table("playground_parser")
public class PlaygroundParser {
private static final long serialVersionUID = 1L;
@Constraint(autoIncrement = true, notNull = true)
private Long id;
@Length(varcharSize = 64)
@Constraint(notNull = true)
private String name; // 解析器名称
@Length(varcharSize = 64)
@Constraint(notNull = true, uniqueKey = "uk_type")
private String type; // 解析器类型标识(唯一)
@Length(varcharSize = 128)
private String displayName; // 显示名称
@Length(varcharSize = 512)
private String description; // 描述
@Length(varcharSize = 64)
private String author; // 作者
@Length(varcharSize = 32)
private String version; // 版本号
@Length(varcharSize = 512)
private String matchPattern; // URL匹配正则
@Length(varcharSize = 65535)
@Constraint(notNull = true)
private String jsCode; // JavaScript代码
@Length(varcharSize = 64)
private String ip; // 创建者IP
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime = new Date(); // 创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime; // 更新时间
private Boolean enabled = true; // 是否启用
}

View File

@@ -0,0 +1,76 @@
package cn.qaiu.lz.web.model;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 演练场测试响应模型
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
@Data
@Builder
public class PlaygroundTestResp {
/**
* 是否执行成功
*/
private boolean success;
/**
* 执行结果(根据方法类型返回不同格式)
* - parse: String (下载链接)
* - parseFileList: List<FileInfo>
* - parseById: String (下载链接)
*/
private Object result;
/**
* 执行日志列表
*/
private List<LogEntry> logs;
/**
* 错误信息
*/
private String error;
/**
* 错误堆栈
*/
private String stackTrace;
/**
* 执行时间(毫秒)
*/
private long executionTime;
/**
* 日志条目
*/
@Data
@Builder
public static class LogEntry {
/**
* 日志级别DEBUG, INFO, WARN, ERROR
*/
private String level;
/**
* 日志消息
*/
private String message;
/**
* 日志时间戳
*/
private long timestamp;
/**
* 日志来源JSJavaScript日志或 JAVAJava日志
*/
private String source;
}
}

View File

@@ -20,4 +20,34 @@ public interface DbService extends BaseAsyncService {
Future<StatisticsInfo> getStatisticsInfo();
/**
* 获取演练场解析器列表
*/
Future<JsonObject> getPlaygroundParserList();
/**
* 保存演练场解析器
*/
Future<JsonObject> savePlaygroundParser(JsonObject parser);
/**
* 更新演练场解析器
*/
Future<JsonObject> updatePlaygroundParser(Long id, JsonObject parser);
/**
* 删除演练场解析器
*/
Future<JsonObject> deletePlaygroundParser(Long id);
/**
* 获取演练场解析器数量
*/
Future<Integer> getPlaygroundParserCount();
/**
* 根据ID获取演练场解析器
*/
Future<JsonObject> getPlaygroundParserById(Long id);
}

View File

@@ -10,10 +10,14 @@ import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.jdbcclient.JDBCPool;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.Tuple;
import io.vertx.sqlclient.templates.SqlTemplate;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* lz-web
@@ -66,4 +70,199 @@ public class DbServiceImpl implements DbService {
});
return promise.future();
}
@Override
public Future<JsonObject> getPlaygroundParserList() {
JDBCPool client = JDBCPoolInit.instance().getPool();
Promise<JsonObject> promise = Promise.promise();
String sql = "SELECT * FROM playground_parser ORDER BY create_time DESC";
client.query(sql).execute().onSuccess(rows -> {
List<JsonObject> list = new ArrayList<>();
for (Row row : rows) {
JsonObject parser = new JsonObject();
parser.put("id", row.getLong("id"));
parser.put("name", row.getString("name"));
parser.put("type", row.getString("type"));
parser.put("displayName", row.getString("display_name"));
parser.put("description", row.getString("description"));
parser.put("author", row.getString("author"));
parser.put("version", row.getString("version"));
parser.put("matchPattern", row.getString("match_pattern"));
parser.put("jsCode", row.getString("js_code"));
parser.put("ip", row.getString("ip"));
// 将LocalDateTime转换为字符串格式避免序列化为数组
var createTime = row.getLocalDateTime("create_time");
if (createTime != null) {
parser.put("createTime", createTime.toString().replace("T", " "));
}
var updateTime = row.getLocalDateTime("update_time");
if (updateTime != null) {
parser.put("updateTime", updateTime.toString().replace("T", " "));
}
parser.put("enabled", row.getBoolean("enabled"));
list.add(parser);
}
promise.complete(JsonResult.data(list).toJsonObject());
}).onFailure(e -> {
log.error("getPlaygroundParserList failed", e);
promise.fail(e);
});
return promise.future();
}
@Override
public Future<JsonObject> savePlaygroundParser(JsonObject parser) {
JDBCPool client = JDBCPoolInit.instance().getPool();
Promise<JsonObject> promise = Promise.promise();
String sql = """
INSERT INTO playground_parser
(name, type, display_name, description, author, version, match_pattern, js_code, ip, create_time, enabled)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?)
""";
client.preparedQuery(sql)
.execute(Tuple.of(
parser.getString("name"),
parser.getString("type"),
parser.getString("displayName"),
parser.getString("description"),
parser.getString("author"),
parser.getString("version"),
parser.getString("matchPattern"),
parser.getString("jsCode"),
parser.getString("ip"),
parser.getBoolean("enabled", true)
))
.onSuccess(res -> {
promise.complete(JsonResult.success("保存成功").toJsonObject());
})
.onFailure(e -> {
log.error("savePlaygroundParser failed", e);
promise.fail(e);
});
return promise.future();
}
@Override
public Future<JsonObject> updatePlaygroundParser(Long id, JsonObject parser) {
JDBCPool client = JDBCPoolInit.instance().getPool();
Promise<JsonObject> promise = Promise.promise();
String sql = """
UPDATE playground_parser
SET name = ?, display_name = ?, description = ?, author = ?,
version = ?, match_pattern = ?, js_code = ?, update_time = NOW(), enabled = ?
WHERE id = ?
""";
client.preparedQuery(sql)
.execute(Tuple.of(
parser.getString("name"),
parser.getString("displayName"),
parser.getString("description"),
parser.getString("author"),
parser.getString("version"),
parser.getString("matchPattern"),
parser.getString("jsCode"),
parser.getBoolean("enabled", true),
id
))
.onSuccess(res -> {
promise.complete(JsonResult.success("更新成功").toJsonObject());
})
.onFailure(e -> {
log.error("updatePlaygroundParser failed", e);
promise.fail(e);
});
return promise.future();
}
@Override
public Future<JsonObject> deletePlaygroundParser(Long id) {
JDBCPool client = JDBCPoolInit.instance().getPool();
Promise<JsonObject> promise = Promise.promise();
String sql = "DELETE FROM playground_parser WHERE id = ?";
client.preparedQuery(sql)
.execute(Tuple.of(id))
.onSuccess(res -> {
promise.complete(JsonResult.success("删除成功").toJsonObject());
})
.onFailure(e -> {
log.error("deletePlaygroundParser failed", e);
promise.fail(e);
});
return promise.future();
}
@Override
public Future<Integer> getPlaygroundParserCount() {
JDBCPool client = JDBCPoolInit.instance().getPool();
Promise<Integer> promise = Promise.promise();
String sql = "SELECT COUNT(*) as count FROM playground_parser";
client.query(sql).execute().onSuccess(rows -> {
Integer count = rows.iterator().next().getInteger("count");
promise.complete(count);
}).onFailure(e -> {
log.error("getPlaygroundParserCount failed", e);
promise.fail(e);
});
return promise.future();
}
@Override
public Future<JsonObject> getPlaygroundParserById(Long id) {
JDBCPool client = JDBCPoolInit.instance().getPool();
Promise<JsonObject> promise = Promise.promise();
String sql = "SELECT * FROM playground_parser WHERE id = ?";
client.preparedQuery(sql)
.execute(Tuple.of(id))
.onSuccess(rows -> {
if (rows.size() > 0) {
Row row = rows.iterator().next();
JsonObject parser = new JsonObject();
parser.put("id", row.getLong("id"));
parser.put("name", row.getString("name"));
parser.put("type", row.getString("type"));
parser.put("displayName", row.getString("display_name"));
parser.put("description", row.getString("description"));
parser.put("author", row.getString("author"));
parser.put("version", row.getString("version"));
parser.put("matchPattern", row.getString("match_pattern"));
parser.put("jsCode", row.getString("js_code"));
parser.put("ip", row.getString("ip"));
// 将LocalDateTime转换为字符串格式避免序列化为数组
var createTime = row.getLocalDateTime("create_time");
if (createTime != null) {
parser.put("createTime", createTime.toString().replace("T", " "));
}
var updateTime = row.getLocalDateTime("update_time");
if (updateTime != null) {
parser.put("updateTime", updateTime.toString().replace("T", " "));
}
parser.put("enabled", row.getBoolean("enabled"));
promise.complete(JsonResult.data(parser).toJsonObject());
} else {
promise.fail("解析器不存在");
}
})
.onFailure(e -> {
log.error("getPlaygroundParserById failed", e);
promise.fail(e);
});
return promise.future();
}
}

View File

@@ -0,0 +1,115 @@
### Playground 安全测试用例集合
### 用于验证JavaScript执行环境的安全性
### 测试1: 系统命令执行
POST http://localhost:9000/v2/playground/test
Content-Type: application/json
{
"jsCode": "// ==UserScript==\n// @name 危险测试-系统命令执行\n// @type security_test\n// @match https://test.com/*\n// ==/UserScript==\n\nfunction parse(shareLinkInfo, http, logger) {\n logger.info('尝试执行系统命令...');\n \n try {\n // 尝试1: 直接访问Runtime类执行命令\n var Runtime = Java.type('java.lang.Runtime');\n var runtime = Runtime.getRuntime();\n var process = runtime.exec('whoami');\n var reader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()));\n var output = reader.readLine();\n logger.error('【安全漏洞】成功执行系统命令: ' + output);\n return '危险: 系统命令执行成功 - ' + output;\n } catch (e) {\n logger.info('Runtime.exec失败: ' + e.message);\n }\n \n try {\n // 尝试2: 使用ProcessBuilder\n var ProcessBuilder = Java.type('java.lang.ProcessBuilder');\n var pb = new ProcessBuilder(['ls', '-la']);\n var process = pb.start();\n logger.error('【安全漏洞】ProcessBuilder执行成功');\n return '危险: ProcessBuilder执行成功';\n } catch (e) {\n logger.info('ProcessBuilder失败: ' + e.message);\n }\n \n return '✓ 安全: 无法执行系统命令';\n}",
"shareUrl": "https://test.com/share/test123",
"pwd": "",
"method": "parse"
}
### 测试2: 文件系统访问
POST http://localhost:9000/v2/playground/test
Content-Type: application/json
{
"jsCode": "// ==UserScript==\n// @name 危险测试-文件系统访问\n// @type security_test\n// @match https://test.com/*\n// ==/UserScript==\n\nfunction parse(shareLinkInfo, http, logger) {\n logger.info('尝试访问文件系统...');\n \n try {\n var Files = Java.type('java.nio.file.Files');\n var Paths = Java.type('java.nio.file.Paths');\n var path = Paths.get('/etc/passwd');\n var content = Files.readAllLines(path);\n logger.error('【安全漏洞】成功读取文件: ' + content.get(0));\n return '危险: 文件读取成功';\n } catch (e) {\n logger.info('文件读取失败: ' + e.message);\n }\n \n try {\n var FileWriter = Java.type('java.io.FileWriter');\n var writer = new FileWriter('/tmp/security_test.txt');\n writer.write('security test');\n writer.close();\n logger.error('【安全漏洞】成功写入文件');\n return '危险: 文件写入成功';\n } catch (e) {\n logger.info('文件写入失败: ' + e.message);\n }\n \n return '✓ 安全: 无法访问文件系统';\n}",
"shareUrl": "https://test.com/share/test123",
"pwd": "",
"method": "parse"
}
### 测试3: 系统属性和环境变量访问
POST http://localhost:9000/v2/playground/test
Content-Type: application/json
{
"jsCode": "// ==UserScript==\n// @name 危险测试-系统属性访问\n// @type security_test\n// @match https://test.com/*\n// ==/UserScript==\n\nfunction parse(shareLinkInfo, http, logger) {\n logger.info('尝试访问系统属性...');\n \n try {\n var System = Java.type('java.lang.System');\n var userHome = System.getProperty('user.home');\n var userName = System.getProperty('user.name');\n var osName = System.getProperty('os.name');\n logger.error('【安全漏洞】系统属性 - HOME: ' + userHome + ', USER: ' + userName + ', OS: ' + osName);\n return '危险: 系统属性访问成功';\n } catch (e) {\n logger.info('系统属性访问失败: ' + e.message);\n }\n \n try {\n var System = Java.type('java.lang.System');\n var env = System.getenv();\n var path = env.get('PATH');\n logger.error('【安全漏洞】环境变量 PATH: ' + path);\n return '危险: 环境变量访问成功';\n } catch (e) {\n logger.info('环境变量访问失败: ' + e.message);\n }\n \n return '✓ 安全: 无法访问系统属性';\n}",
"shareUrl": "https://test.com/share/test123",
"pwd": "",
"method": "parse"
}
### 测试4: 反射攻击
POST http://localhost:9000/v2/playground/test
Content-Type: application/json
{
"jsCode": "// ==UserScript==\n// @name 危险测试-反射攻击\n// @type security_test\n// @match https://test.com/*\n// ==/UserScript==\n\nfunction parse(shareLinkInfo, http, logger) {\n logger.info('尝试使用反射...');\n \n try {\n var Class = Java.type('java.lang.Class');\n var systemClass = Class.forName('java.lang.System');\n var methods = systemClass.getDeclaredMethods();\n logger.error('【安全漏洞】反射访问成功System类有 ' + methods.length + ' 个方法');\n return '危险: 反射访问成功';\n } catch (e) {\n logger.info('Class.forName失败: ' + e.message);\n }\n \n try {\n var Thread = Java.type('java.lang.Thread');\n var classLoader = Thread.currentThread().getContextClassLoader();\n logger.error('【安全漏洞】获取到ClassLoader: ' + classLoader);\n return '危险: ClassLoader访问成功';\n } catch (e) {\n logger.info('ClassLoader访问失败: ' + e.message);\n }\n \n return '✓ 安全: 无法使用反射';\n}",
"shareUrl": "https://test.com/share/test123",
"pwd": "",
"method": "parse"
}
### 测试5: 网络Socket连接
POST http://localhost:9000/v2/playground/test
Content-Type: application/json
{
"jsCode": "// ==UserScript==\n// @name 危险测试-网络连接\n// @type security_test\n// @match https://test.com/*\n// ==/UserScript==\n\nfunction parse(shareLinkInfo, http, logger) {\n logger.info('尝试创建网络连接...');\n \n try {\n var Socket = Java.type('java.net.Socket');\n var socket = new Socket('127.0.0.1', 9000);\n logger.error('【安全漏洞】Socket连接成功');\n socket.close();\n return '危险: Socket连接成功';\n } catch (e) {\n logger.info('Socket连接失败: ' + e.message);\n }\n \n try {\n var URL = Java.type('java.net.URL');\n var url = new URL('http://localhost:9000');\n var conn = url.openConnection();\n conn.connect();\n logger.error('【安全漏洞】URL连接成功');\n return '危险: URL连接成功';\n } catch (e) {\n logger.info('URL连接失败: ' + e.message);\n }\n \n return '✓ 安全: 无法创建网络连接';\n}",
"shareUrl": "https://test.com/share/test123",
"pwd": "",
"method": "parse"
}
### 测试6: JVM退出攻击
POST http://localhost:9000/v2/playground/test
Content-Type: application/json
{
"jsCode": "// ==UserScript==\n// @name 危险测试-JVM退出\n// @type security_test\n// @match https://test.com/*\n// ==/UserScript==\n\nfunction parse(shareLinkInfo, http, logger) {\n logger.info('尝试退出JVM...');\n \n try {\n var System = Java.type('java.lang.System');\n logger.warn('准备执行 System.exit(1)...');\n System.exit(1);\n return '危险: JVM退出成功';\n } catch (e) {\n logger.info('System.exit失败: ' + e.message);\n }\n \n try {\n var Runtime = Java.type('java.lang.Runtime');\n Runtime.getRuntime().halt(1);\n return '危险: Runtime.halt成功';\n } catch (e) {\n logger.info('Runtime.halt失败: ' + e.message);\n }\n \n return '✓ 安全: 无法退出JVM';\n}",
"shareUrl": "https://test.com/share/test123",
"pwd": "",
"method": "parse"
}
### 测试7: HTTP客户端SSRF攻击
POST http://localhost:9000/v2/playground/test
Content-Type: application/json
{
"jsCode": "// ==UserScript==\n// @name 危险测试-SSRF攻击\n// @type security_test\n// @match https://test.com/*\n// ==/UserScript==\n\nfunction parse(shareLinkInfo, http, logger) {\n logger.info('测试HTTP客户端SSRF风险...');\n \n try {\n // 尝试访问内网地址\n logger.info('尝试访问本地服务...');\n var response = http.get('http://127.0.0.1:9000/v2/health');\n logger.warn('【潜在风险】可以访问内网地址,响应长度: ' + response.length);\n return '⚠ 警告: HTTP客户端可访问内网 (SSRF风险)';\n } catch (e) {\n logger.info('内网访问失败: ' + e.message);\n }\n \n try {\n // 尝试访问云服务元数据API (AWS/阿里云等)\n logger.info('尝试访问云服务元数据API...');\n var response = http.get('http://169.254.169.254/latest/meta-data/');\n logger.error('【严重漏洞】可以访问云服务元数据!');\n return '危险: 可访问云服务元数据';\n } catch (e) {\n logger.info('元数据API访问失败: ' + e.message);\n }\n \n return '✓ 提示: HTTP客户端功能正常';\n}",
"shareUrl": "https://test.com/share/test123",
"pwd": "",
"method": "parse"
}
### 测试8: 尝试访问注入对象的私有方法
POST http://localhost:9000/v2/playground/test
Content-Type: application/json
{
"jsCode": "// ==UserScript==\n// @name 危险测试-对象滥用\n// @type security_test\n// @match https://test.com/*\n// ==/UserScript==\n\nfunction parse(shareLinkInfo, http, logger) {\n logger.info('尝试滥用注入的对象...');\n \n try {\n // 尝试获取http对象的类信息\n var httpClass = http.getClass();\n logger.warn('HTTP客户端类名: ' + httpClass.getName());\n \n var methods = httpClass.getDeclaredMethods();\n logger.warn('HTTP客户端有 ' + methods.length + ' 个方法');\n \n // 列出所有方法\n for (var i = 0; i < Math.min(methods.length, 10); i++) {\n logger.info('方法' + i + ': ' + methods[i].getName());\n }\n \n return '⚠ 警告: 可以通过反射访问注入对象';\n } catch (e) {\n logger.info('对象反射失败: ' + e.message);\n }\n \n try {\n // 尝试获取shareLinkInfo的内部数据\n var infoClass = shareLinkInfo.getClass();\n var fields = infoClass.getDeclaredFields();\n logger.warn('ShareLinkInfo有 ' + fields.length + ' 个字段');\n return '⚠ 警告: 可以访问对象内部结构';\n } catch (e) {\n logger.info('字段访问失败: ' + e.message);\n }\n \n return '✓ 安全: 对象访问受限';\n}",
"shareUrl": "https://test.com/share/test123",
"pwd": "",
"method": "parse"
}
### 测试9: 无限循环DOS攻击
POST http://localhost:9000/v2/playground/test
Content-Type: application/json
{
"jsCode": "// ==UserScript==\n// @name 危险测试-DOS攻击\n// @type security_test\n// @match https://test.com/*\n// ==/UserScript==\n\nfunction parse(shareLinkInfo, http, logger) {\n logger.info('测试DOS防护...');\n \n try {\n logger.warn('准备执行5秒的计算密集操作...');\n var startTime = new Date().getTime();\n var count = 0;\n \n // 执行5秒的计算\n while (new Date().getTime() - startTime < 5000) {\n count++;\n // 每100万次记录一次\n if (count % 1000000 === 0) {\n logger.info('已执行 ' + (count/1000000) + ' 百万次计算');\n }\n }\n \n logger.warn('⚠ 警告: 可执行长时间计算,计数: ' + count);\n return '⚠ 警告: 无超时限制 (DOS风险)';\n } catch (e) {\n logger.info('计算被中断: ' + e.message);\n return '✓ 安全: 存在执行时间限制';\n }\n}",
"shareUrl": "https://test.com/share/test123",
"pwd": "",
"method": "parse"
}
### 测试10: 内存溢出攻击
POST http://localhost:9000/v2/playground/test
Content-Type: application/json
{
"jsCode": "// ==UserScript==\n// @name 危险测试-内存攻击\n// @type security_test\n// @match https://test.com/*\n// ==/UserScript==\n\nfunction parse(shareLinkInfo, http, logger) {\n logger.info('测试内存限制...');\n \n try {\n logger.warn('准备创建大量字符串对象...');\n var arrays = [];\n \n // 尝试创建100个大数组\n for (var i = 0; i < 100; i++) {\n arrays.push(new Array(1000000).fill('x'.repeat(100)));\n if (i % 10 === 0) {\n logger.info('已创建 ' + i + ' 个大数组');\n }\n }\n \n logger.error('【潜在风险】成功创建大量对象,可能导致内存问题');\n return '⚠ 警告: 无内存限制';\n } catch (e) {\n logger.info('内存分配失败: ' + e.message);\n return '✓ 安全: 存在内存限制';\n }\n}",
"shareUrl": "https://test.com/share/test123",
"pwd": "",
"method": "parse"
}
###