├── README.md
├── assets
├── image-20250222011558648.png
├── image-20250222012127096.png
├── image-20250222012232207.png
├── image-20250222012315255.png
├── image-20250222012445385.png
├── image-20250222012838474.png
├── image-20250222012916552.png
└── image-20250222014422442.png
├── dependency-reduced-pom.xml
├── pom.xml
└── src
└── main
├── java
└── com
│ └── cyberscanner
│ ├── ColoredMessageCallback.java
│ ├── CyberScannerApp.java
│ ├── ProgressCallback.java
│ ├── ScanTask.java
│ └── StatusBar.java
└── resources
├── config.yaml
└── styles
└── cyber-theme.css
/README.md:
--------------------------------------------------------------------------------
1 | # CyberMatrix 量子安全分析引擎
2 |
3 | CyberMatrix 是一个基于 AI 的代码安全分析工具,专注于自动化检测和分析代码中的潜在安全漏洞。采用赛博朋克风格的现代化界面,提供直观的安全分析体验。
4 |
5 | 
6 |
7 | ## 核心功能
8 |
9 | ### 1. 深度安全分析
10 | - 自动识别代码中的安全风险点
11 | - 支持检测 SQL 注入、XSS、CSRF、文件上传漏洞等常见安全问题
12 | - 基于 CVSS 评分标准评估风险等级
13 | - 提供详细的漏洞位置和描述
14 |
15 | 
16 |
17 | ### 2. Webshell 检测
18 | - 智能识别 PHP/JSP/ASP 等 WebShell 特征
19 | - 检测内存马和无文件落地木马
20 | - 分析可疑的代码执行和文件操作
21 | - 识别混淆编码和加密规避技术
22 |
23 | 
24 |
25 | ### 3. 智能分析引擎
26 | - 基于大语言模型的智能代码理解
27 | - 支持多种编程语言的代码分析
28 | - 极低的误报率和高准确度
29 | - 持续学习和优化的检测能力
30 |
31 | ## 界面特点
32 |
33 | - 赛博朋克风格界面设计
34 | - 实时进度展示和分析反馈
35 | - 直观的文件树浏览
36 | - 智能的颜色标记系统
37 | - 流畅的动画效果
38 |
39 |
40 |

41 |

42 |
43 |
44 | ## 技术栈
45 |
46 | - JavaFX + JFoenix:现代化 UI 框架
47 | - Ollama:本地化 AI 模型
48 | - AnimateFX:界面动画效果
49 | - Jackson:JSON 处理
50 | - OkHttp:网络请求
51 |
52 | ## 使用方法
53 |
54 | 1. 点击"初始化量子扫描目标"选择要分析的代码目录
55 | 2. 选择分析模式(深度安全分析/Webshell检测)
56 | 3. 可选择是否包含静态资源分析
57 | 4. 点击"启动量子安全分析"开始扫描
58 | 5. 实时查看分析结果和进度
59 |
60 |
80 |
81 |
82 |
83 | ## 配置要求
84 |
85 | - Java 8 或更高版本
86 | - Ollama 本地服务
87 | - 建议系统内存 8GB 以上
88 |
89 | ## 安装说明
90 |
91 | 1. 确保已安装 Java 8 运行环境
92 | 2. 下载并安装 Ollama
93 | 3. 配置 config.yaml 文件
94 | 4. 运行启动脚本
95 |
96 | ## 开发环境搭建
97 |
98 | 1. Clone 项目代码
99 | 2. 使用 Maven 导入依赖
100 | 3. 配置 JDK 8
101 | 4. 运行 CyberScannerApp 主类
102 |
103 | ## 参考项目
104 |
105 | 本项目受以下开源项目启发:
106 | - [DeepSeekSelfTool](https://github.com/ChinaRan0/DeepSeekSelfTool) - 基于 DeepSeek 的代码安全分析工具
107 |
108 | ## 许可证
109 |
110 | MIT License
--------------------------------------------------------------------------------
/assets/image-20250222011558648.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/savior-only/CyberMatrix/a4260b3d42b20d2f3b6afa0053a959b94fa0d394/assets/image-20250222011558648.png
--------------------------------------------------------------------------------
/assets/image-20250222012127096.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/savior-only/CyberMatrix/a4260b3d42b20d2f3b6afa0053a959b94fa0d394/assets/image-20250222012127096.png
--------------------------------------------------------------------------------
/assets/image-20250222012232207.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/savior-only/CyberMatrix/a4260b3d42b20d2f3b6afa0053a959b94fa0d394/assets/image-20250222012232207.png
--------------------------------------------------------------------------------
/assets/image-20250222012315255.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/savior-only/CyberMatrix/a4260b3d42b20d2f3b6afa0053a959b94fa0d394/assets/image-20250222012315255.png
--------------------------------------------------------------------------------
/assets/image-20250222012445385.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/savior-only/CyberMatrix/a4260b3d42b20d2f3b6afa0053a959b94fa0d394/assets/image-20250222012445385.png
--------------------------------------------------------------------------------
/assets/image-20250222012838474.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/savior-only/CyberMatrix/a4260b3d42b20d2f3b6afa0053a959b94fa0d394/assets/image-20250222012838474.png
--------------------------------------------------------------------------------
/assets/image-20250222012916552.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/savior-only/CyberMatrix/a4260b3d42b20d2f3b6afa0053a959b94fa0d394/assets/image-20250222012916552.png
--------------------------------------------------------------------------------
/assets/image-20250222014422442.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/savior-only/CyberMatrix/a4260b3d42b20d2f3b6afa0053a959b94fa0d394/assets/image-20250222014422442.png
--------------------------------------------------------------------------------
/dependency-reduced-pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.cyberscanner
5 | cyber-scanner
6 | 1.0-SNAPSHOT
7 |
8 |
9 |
10 | maven-compiler-plugin
11 | 3.8.1
12 |
13 | 1.8
14 | 1.8
15 |
16 |
17 |
18 | maven-shade-plugin
19 | 3.2.4
20 |
21 |
22 | package
23 |
24 | shade
25 |
26 |
27 |
28 |
29 | com.cyberscanner.CyberScannerApp
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | com.itextpdf
41 | itext7-core
42 | 7.2.5
43 | pom
44 | compile
45 |
46 |
47 |
48 | 1.8
49 | UTF-8
50 | 8.0.202
51 | 1.8
52 |
53 |
54 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.cyberscanner
8 | cyber-scanner
9 | 1.0-SNAPSHOT
10 |
11 |
12 | UTF-8
13 | 1.8
14 | 1.8
15 |
16 |
17 |
18 |
19 | com.jfoenix
20 | jfoenix
21 | 8.0.10
22 |
23 |
24 |
25 |
26 | org.yaml
27 | snakeyaml
28 | 1.33
29 |
30 |
31 |
32 |
33 | com.squareup.okhttp3
34 | okhttp
35 | 3.14.9
36 |
37 |
38 |
39 |
40 | com.fasterxml.jackson.core
41 | jackson-databind
42 | 2.12.7.1
43 |
44 |
45 |
46 |
47 | io.github.typhon0
48 | AnimateFX
49 | 1.2.1
50 |
51 |
52 |
53 |
54 | ch.qos.logback
55 | logback-classic
56 | 1.2.9
57 |
58 |
59 |
60 |
61 |
62 |
63 | org.apache.maven.plugins
64 | maven-compiler-plugin
65 | 3.8.1
66 |
67 | 1.8
68 | 1.8
69 |
70 |
71 |
72 | org.apache.maven.plugins
73 | maven-shade-plugin
74 | 3.2.4
75 |
76 |
77 | package
78 |
79 | shade
80 |
81 |
82 | false
83 |
84 |
85 | *:*
86 |
87 | META-INF/*.SF
88 | META-INF/*.DSA
89 | META-INF/*.RSA
90 | config.yaml
91 |
92 |
93 |
94 |
95 |
96 | com.cyberscanner.CyberScannerApp
97 |
98 |
99 |
100 | META-INF/services/javax.annotation.processing.Processor
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/src/main/java/com/cyberscanner/ColoredMessageCallback.java:
--------------------------------------------------------------------------------
1 | package com.cyberscanner;
2 |
3 | import javafx.scene.paint.Color;
4 |
5 | public interface ColoredMessageCallback {
6 | void updateMessage(String message, Color color);
7 | }
--------------------------------------------------------------------------------
/src/main/java/com/cyberscanner/CyberScannerApp.java:
--------------------------------------------------------------------------------
1 | package com.cyberscanner;
2 |
3 | import animatefx.animation.Bounce;
4 | import animatefx.animation.FadeIn;
5 | import animatefx.animation.Pulse;
6 | import javafx.animation.KeyFrame;
7 | import javafx.animation.KeyValue;
8 | import javafx.animation.Timeline;
9 | import javafx.scene.effect.DropShadow;
10 | import javafx.scene.effect.Glow;
11 | import javafx.util.Duration;
12 | import com.jfoenix.controls.*;
13 | import javafx.application.Application;
14 | import javafx.geometry.Insets;
15 | import javafx.geometry.Pos;
16 | import javafx.scene.Scene;
17 | import javafx.scene.control.*;
18 | import javafx.scene.layout.*;
19 | import javafx.scene.text.Text;
20 | import javafx.scene.paint.Color;
21 | import javafx.stage.DirectoryChooser;
22 | import javafx.stage.Stage;
23 |
24 | import java.io.File;
25 | import java.util.Arrays;
26 | import java.util.Collections;
27 |
28 | public class CyberScannerApp extends Application {
29 | private ScanTask currentTask;
30 |
31 | private JFXTextArea resultDisplay;
32 | private JFXButton scanButton;
33 | private JFXButton selectButton;
34 | private Label pathLabel;
35 | private TreeView fileTree;
36 | private JFXRadioButton auditRadio;
37 | private JFXRadioButton webshellRadio;
38 | private JFXCheckBox auditJsCheckbox;
39 | private StatusBar statusBar;
40 | private JFXProgressBar progressBar;
41 |
42 | @Override
43 | public void start(Stage primaryStage) {
44 | primaryStage.setOnCloseRequest(event -> {
45 | if (currentTask != null) {
46 | currentTask.stop();
47 | }
48 | });
49 | primaryStage.setTitle("CyberMatrix - 量子安全分析引擎");
50 | primaryStage.setMinWidth(1280);
51 | primaryStage.setMinHeight(720);
52 |
53 | // 创建主布局
54 | BorderPane mainLayout = new BorderPane();
55 | mainLayout.getStyleClass().add("main-layout");
56 | mainLayout.setPrefSize(1280, 720);
57 |
58 | // 左侧面板
59 | VBox leftPanel = createLeftPanel();
60 | leftPanel.setMinWidth(300);
61 | leftPanel.setPrefWidth(300);
62 | leftPanel.setMaxWidth(400);
63 | VBox.setVgrow(fileTree, Priority.ALWAYS);
64 | mainLayout.setLeft(leftPanel);
65 |
66 | // 右侧结果显示区
67 | resultDisplay = new JFXTextArea();
68 | resultDisplay.getStyleClass().add("result-display");
69 | resultDisplay.setEditable(false);
70 | resultDisplay.setWrapText(true);
71 | BorderPane.setMargin(resultDisplay, new Insets(10));
72 | VBox.setVgrow(resultDisplay, Priority.ALWAYS);
73 | mainLayout.setCenter(resultDisplay);
74 |
75 | // 进度条
76 | progressBar = new JFXProgressBar();
77 | progressBar.setProgress(0);
78 | progressBar.getStyleClass().add("progress-bar");
79 | progressBar.setPrefWidth(Double.MAX_VALUE);
80 |
81 | // 状态栏
82 | statusBar = new StatusBar();
83 | statusBar.getStyleClass().add("status-bar");
84 |
85 | // 将进度条和状态栏放在底部
86 | VBox bottomBox = new VBox(5);
87 | bottomBox.setPadding(new Insets(5));
88 | bottomBox.getChildren().addAll(progressBar, statusBar);
89 | mainLayout.setBottom(bottomBox);
90 |
91 | // 场景和样式
92 | Scene scene = new Scene(mainLayout);
93 | scene.getStylesheets().add(getClass().getResource("/styles/cyber-theme.css").toExternalForm());
94 |
95 | primaryStage.setScene(scene);
96 | primaryStage.show();
97 |
98 | // 添加启动动画序列
99 | new FadeIn(mainLayout).play();
100 | new Pulse(selectButton).play();
101 |
102 | // 为pathLabel添加发光效果动画
103 | Glow glow = new Glow(0);
104 | pathLabel.setEffect(glow);
105 | Timeline glowTimeline = new Timeline(
106 | new KeyFrame(Duration.ZERO, new KeyValue(glow.levelProperty(), 0)),
107 | new KeyFrame(Duration.seconds(1), new KeyValue(glow.levelProperty(), 0.8)),
108 | new KeyFrame(Duration.seconds(2), new KeyValue(glow.levelProperty(), 0))
109 | );
110 | glowTimeline.setCycleCount(Timeline.INDEFINITE);
111 | glowTimeline.play();
112 |
113 | // 添加周期性动画效果
114 | Timeline pulseTimeline = new Timeline(
115 | new KeyFrame(Duration.seconds(2),
116 | new KeyValue(leftPanel.effectProperty(),
117 | new DropShadow(10, Color.valueOf("#4d4dff"))))
118 | );
119 | pulseTimeline.setAutoReverse(true);
120 | pulseTimeline.setCycleCount(Timeline.INDEFINITE);
121 | pulseTimeline.play();
122 | }
123 |
124 | private VBox createLeftPanel() {
125 | VBox leftPanel = new VBox(10);
126 | leftPanel.getStyleClass().add("left-panel");
127 | leftPanel.setPadding(new Insets(10));
128 |
129 | // 目录选择按钮
130 | selectButton = new JFXButton("🌐 初始化量子扫描目标");
131 | selectButton.getStyleClass().add("cyber-button");
132 | selectButton.setOnAction(e -> selectDirectory());
133 |
134 | // 路径显示标签
135 | pathLabel = new Label("等待目标初始化...");
136 | pathLabel.getStyleClass().add("path-label");
137 |
138 | // 模式选择组
139 | VBox modeBox = createModeSelectionBox();
140 |
141 | // 文件树
142 | fileTree = new TreeView<>();
143 | fileTree.getStyleClass().add("file-tree");
144 | VBox.setVgrow(fileTree, Priority.ALWAYS);
145 |
146 | // 扫描按钮
147 | scanButton = new JFXButton("⚡ 启动量子安全分析");
148 | scanButton.getStyleClass().add("scan-button");
149 | scanButton.setDisable(true);
150 | scanButton.setOnAction(e -> startScan());
151 |
152 | leftPanel.getChildren().addAll(
153 | selectButton,
154 | pathLabel,
155 | modeBox,
156 | auditJsCheckbox,
157 | fileTree,
158 | scanButton
159 | );
160 |
161 | return leftPanel;
162 | }
163 |
164 | private VBox createModeSelectionBox() {
165 | VBox modeBox = new VBox(5);
166 | modeBox.getStyleClass().add("mode-box");
167 |
168 | Label modeLabel = new Label("🔧 分析模式");
169 | modeLabel.getStyleClass().add("mode-label");
170 |
171 | ToggleGroup modeGroup = new ToggleGroup();
172 | auditRadio = new JFXRadioButton("深度安全分析");
173 | webshellRadio = new JFXRadioButton("Webshell检测");
174 |
175 | auditRadio.setToggleGroup(modeGroup);
176 | webshellRadio.setToggleGroup(modeGroup);
177 | auditRadio.setSelected(true);
178 |
179 | auditJsCheckbox = new JFXCheckBox("包含静态资源分析");
180 | auditJsCheckbox.setSelected(true);
181 |
182 | modeBox.getChildren().addAll(modeLabel, auditRadio, webshellRadio);
183 | return modeBox;
184 | }
185 |
186 | private void selectDirectory() {
187 | DirectoryChooser directoryChooser = new DirectoryChooser();
188 | directoryChooser.setTitle("选择代码矩阵接入点");
189 | File selectedDirectory = directoryChooser.showDialog(null);
190 |
191 | if (selectedDirectory != null) {
192 | pathLabel.setText("📂 目标:" + selectedDirectory.getName());
193 | updateFileTree(selectedDirectory);
194 | scanButton.setDisable(false);
195 | statusBar.setStatus("✅ 目标初始化完成");
196 |
197 | // 添加动画效果
198 | new Pulse(scanButton).play();
199 | }
200 | }
201 |
202 | private int totalFileCount = 0;
203 |
204 | private void updateFileTree(File root) {
205 | TreeItem rootItem = new TreeItem<>(root);
206 | rootItem.setExpanded(true);
207 | fileTree.setRoot(rootItem);
208 | totalFileCount = 0;
209 | populateFileTree(rootItem);
210 |
211 | // 设置单元格工厂来自定义显示
212 | fileTree.setCellFactory(tv -> new TreeCell() {
213 | @Override
214 | protected void updateItem(File item, boolean empty) {
215 | super.updateItem(item, empty);
216 | if (empty || item == null) {
217 | setText(null);
218 | } else {
219 | // 只显示文件或目录名,而不是完整路径
220 | setText(item.getName());
221 | }
222 | }
223 | });
224 | }
225 |
226 | private void populateFileTree(TreeItem item) {
227 | File[] files = item.getValue().listFiles();
228 | if (files != null) {
229 | Arrays.sort(files, (f1, f2) -> {
230 | // 目录优先,然后按名称排序
231 | if (f1.isDirectory() && !f2.isDirectory()) {
232 | return -1;
233 | } else if (!f1.isDirectory() && f2.isDirectory()) {
234 | return 1;
235 | } else {
236 | return f1.getName().compareToIgnoreCase(f2.getName());
237 | }
238 | });
239 |
240 | for (File file : files) {
241 | TreeItem fileItem = new TreeItem<>(file);
242 | item.getChildren().add(fileItem);
243 | if (!file.isDirectory()) {
244 | totalFileCount++;
245 | }
246 | if (file.isDirectory()) {
247 | populateFileTree(fileItem);
248 | }
249 | }
250 | }
251 | }
252 |
253 | private void startScan() {
254 | if (currentTask != null) {
255 | currentTask.stop();
256 | }
257 | if (fileTree.getRoot() == null) {
258 | showAlert("警告", "请先选择代码目录!");
259 | return;
260 | }
261 |
262 | scanButton.setDisable(true);
263 | String initMsg = auditRadio.isSelected() ?
264 | "🚀 正在启动深度安全分析引擎..." :
265 | "🕵️ 正在启动Webshell检测引擎...";
266 |
267 | resultDisplay.setText(initMsg + "\n" + String.join("", Collections.nCopies(50, "▮")) + "\n");
268 |
269 | // 创建并启动扫描任务
270 | currentTask = new ScanTask(
271 | fileTree.getRoot().getValue(),
272 | auditRadio.isSelected(),
273 | auditJsCheckbox.isSelected(),
274 | this::updateProgress,
275 | this::updateStatus,
276 | this::showResults
277 | );
278 | new Thread(currentTask).start();
279 | }
280 |
281 | private void updateProgress(double progress) {
282 | javafx.application.Platform.runLater(() -> {
283 | progressBar.setProgress(progress);
284 | });
285 | }
286 |
287 | private void updateStatus(String message, javafx.scene.paint.Color color) {
288 | javafx.application.Platform.runLater(() -> {
289 | statusBar.setStatus(message);
290 | String style = String.format("-fx-text-fill: %s;", color.toString().replace("0x", "#"));
291 | Text text = new Text("⚡ " + message + "\n");
292 | text.setStyle(style);
293 | resultDisplay.appendText(text.getText());
294 | resultDisplay.setStyle(style);
295 | });
296 | }
297 |
298 | private void showResults(String report) {
299 | javafx.application.Platform.runLater(() -> {
300 | scanButton.setDisable(false);
301 |
302 | if (auditRadio.isSelected()) {
303 | String header = "\n📊 深度安全分析完成!统计信息:\n";
304 |
305 | // 解析报告并生成统计信息
306 | String[] lines = report.split("\n");
307 | int highRiskIssues = 0;
308 | int mediumRiskIssues = 0;
309 |
310 | for (String line : lines) {
311 | if (line.contains("[高危]")) highRiskIssues++;
312 | if (line.contains("[中危]")) mediumRiskIssues++;
313 | }
314 |
315 | int totalIssues = highRiskIssues + mediumRiskIssues;
316 | StringBuilder statisticsBuilder = new StringBuilder();
317 | statisticsBuilder.append(String.format(
318 | "总扫描文件数:%d\n" +
319 | "高危问题数:%d\n" +
320 | "中危问题数:%d\n" +
321 | "问题总计:%d\n",
322 | totalFileCount, highRiskIssues, mediumRiskIssues, totalIssues
323 | ));
324 |
325 | resultDisplay.appendText(header + statisticsBuilder.toString());
326 | } else {
327 | resultDisplay.appendText("\n🎯 Webshell检测完成!");
328 | }
329 |
330 | statusBar.setStatus("✅ 扫描完成");
331 |
332 | // 添加完成动画
333 | new Bounce(resultDisplay).play();
334 | });
335 | }
336 |
337 | private void showAlert(String title, String content) {
338 | Alert alert = new Alert(Alert.AlertType.WARNING);
339 | alert.setTitle(title);
340 | alert.setHeaderText(null);
341 | alert.setContentText(content);
342 | alert.showAndWait();
343 | }
344 |
345 | public static void main(String[] args) {
346 | launch(args);
347 | }
348 | }
--------------------------------------------------------------------------------
/src/main/java/com/cyberscanner/ProgressCallback.java:
--------------------------------------------------------------------------------
1 | package com.cyberscanner;
2 |
3 | @FunctionalInterface
4 | public interface ProgressCallback {
5 | void updateProgress(double progress);
6 | }
7 |
8 | @FunctionalInterface
9 | interface MessageCallback {
10 | void updateMessage(String message);
11 | }
--------------------------------------------------------------------------------
/src/main/java/com/cyberscanner/ScanTask.java:
--------------------------------------------------------------------------------
1 | package com.cyberscanner;
2 |
3 | import com.fasterxml.jackson.databind.JsonNode;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import okhttp3.*;
6 | import org.yaml.snakeyaml.Yaml;
7 |
8 | import java.io.*;
9 | import java.nio.charset.StandardCharsets;
10 | import java.util.*;
11 | import java.util.function.Consumer;
12 |
13 | public class ScanTask implements Runnable {
14 | private volatile boolean isRunning = true;
15 | private final File rootDirectory;
16 | private final boolean isAuditMode;
17 | private final boolean includeJsFiles;
18 | private final ProgressCallback progressCallback;
19 | private final ColoredMessageCallback messageCallback;
20 | private final Consumer resultCallback;
21 | private final OkHttpClient httpClient;
22 | private final ObjectMapper objectMapper;
23 | private String ollamaHost;
24 | private String ollamaModel;
25 |
26 | public ScanTask(File rootDirectory, boolean isAuditMode, boolean includeJsFiles,
27 | ProgressCallback progressCallback, ColoredMessageCallback messageCallback, Consumer resultCallback) {
28 | this.rootDirectory = rootDirectory;
29 | this.isAuditMode = isAuditMode;
30 | this.includeJsFiles = includeJsFiles;
31 | this.progressCallback = progressCallback;
32 | this.messageCallback = messageCallback;
33 | this.resultCallback = resultCallback;
34 | this.httpClient = new OkHttpClient.Builder()
35 | .connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
36 | .writeTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
37 | .readTimeout(60, java.util.concurrent.TimeUnit.SECONDS)
38 | .retryOnConnectionFailure(true)
39 | .build();
40 | this.objectMapper = new ObjectMapper();
41 | loadConfig();
42 | }
43 |
44 | private void loadConfig() {
45 | try {
46 | // 首先尝试从jar包同级目录读取config.yaml
47 | File externalConfig = new File(new File(getClass().getProtectionDomain()
48 | .getCodeSource().getLocation().toURI()).getParent(), "config.yaml");
49 |
50 | InputStream inputStream;
51 | if (externalConfig.exists()) {
52 | inputStream = new FileInputStream(externalConfig);
53 | } else {
54 | // 如果外部配置不存在,尝试从jar包内部读取
55 | inputStream = getClass().getResourceAsStream("/config.yaml");
56 | if (inputStream == null) {
57 | // 如果两个位置都没有配置文件,显示错误对话框
58 | javafx.application.Platform.runLater(() -> {
59 | javafx.scene.control.Alert alert = new javafx.scene.control.Alert(javafx.scene.control.Alert.AlertType.ERROR);
60 | alert.setTitle("配置错误");
61 | alert.setHeaderText("找不到配置文件");
62 | alert.setContentText(String.format("请确保在程序目录 %s 下存在 config.yaml 配置文件。\n\n" +
63 | "配置文件示例内容:\n" +
64 | "api:\n" +
65 | " ollama:\n" +
66 | " url: http://localhost:11434/api\n" +
67 | " model: deepseek-coder",
68 | externalConfig.getParent()));
69 | alert.showAndWait();
70 | System.exit(1);
71 | });
72 | return;
73 | }
74 | }
75 |
76 | Yaml yaml = new Yaml();
77 | Map config = yaml.load(inputStream);
78 |
79 | Map api = (Map) config.get("api");
80 | Map ollama = (Map) api.get("ollama");
81 | String ollamaUrl = (String) ollama.get("url");
82 | this.ollamaHost = ollamaUrl.split("/api")[0];
83 | this.ollamaModel = (String) ollama.get("model");
84 |
85 | inputStream.close();
86 | } catch (Exception e) {
87 | e.printStackTrace();
88 | // 显示错误对话框
89 | javafx.application.Platform.runLater(() -> {
90 | javafx.scene.control.Alert alert = new javafx.scene.control.Alert(javafx.scene.control.Alert.AlertType.ERROR);
91 | alert.setTitle("配置错误");
92 | alert.setHeaderText("配置文件读取失败");
93 | alert.setContentText("请检查config.yaml文件格式是否正确。\n\n错误信息:" + e.getMessage());
94 | alert.showAndWait();
95 | System.exit(1);
96 | });
97 | }
98 | }
99 |
100 | public void stop() {
101 | isRunning = false;
102 | }
103 |
104 | @Override
105 | public void run() {
106 | Map filesContent = scanCodeFiles(rootDirectory);
107 | List results = new ArrayList<>();
108 | int totalFiles = filesContent.size(); // 使用实际扫描的文件数量
109 | int processedFiles = 0;
110 | int detectedFiles = 0; // 新增:检测到问题的文件数量
111 |
112 | // 初始化进度条
113 | javafx.application.Platform.runLater(() -> {
114 | progressCallback.updateProgress(0.0);
115 | });
116 |
117 | for (Map.Entry entry : filesContent.entrySet()) {
118 | if (!isRunning) {
119 | messageCallback.updateMessage("⚠️ 扫描任务已中断", javafx.scene.paint.Color.web("#FFB86C"));
120 | return;
121 | }
122 | String filepath = entry.getKey();
123 | String content = entry.getValue();
124 |
125 | try {
126 | String filename = new File(filepath).getName();
127 | messageCallback.updateMessage(
128 | isAuditMode ?
129 | String.format("🔍 分析 %s... (%d/%d)", filename, processedFiles + 1, totalFiles) :
130 | String.format("🕵️ 扫描 %s... (%d/%d)", filename, processedFiles + 1, totalFiles),
131 | javafx.scene.paint.Color.DODGERBLUE);
132 |
133 | String prompt = createPrompt(content);
134 | String response = callOllamaAPI(prompt);
135 | String result = processResponse(response);
136 |
137 | // 如果检测到问题(包含[高危]标记),增加检测文件计数
138 | if (!isAuditMode && result.contains("[高危]") && !results.stream().anyMatch(r -> r.contains(filepath))) {
139 | detectedFiles++;
140 | }
141 |
142 | // 根据扫描结果中的风险等级设置不同的颜色
143 | String formattedResult = String.format("%s %s\n%s\n%s",
144 | isAuditMode ? "📄" : "📁",
145 | filepath,
146 | result,
147 | String.join("", Collections.nCopies(50, "━")));
148 |
149 | // 根据结果内容设置不同的颜色
150 | javafx.scene.paint.Color messageColor;
151 | if (result.contains("[高危]")) {
152 | messageColor = javafx.scene.paint.Color.web("#FF4444");
153 | } else if (result.contains("[中危]")) {
154 | messageColor = javafx.scene.paint.Color.web("#FFB86C");
155 | } else if (result.contains("[低危]")) {
156 | messageColor = javafx.scene.paint.Color.web("#50FA7B");
157 | } else {
158 | messageColor = javafx.scene.paint.Color.web("#8BE9FD");
159 | }
160 |
161 | messageCallback.updateMessage(formattedResult, messageColor);
162 | results.add(formattedResult);
163 |
164 | // 更新进度
165 | processedFiles++;
166 | double progress = (double) processedFiles / totalFiles;
167 | javafx.application.Platform.runLater(() -> {
168 | progressCallback.updateProgress(progress);
169 | });
170 | } catch (Exception e) {
171 | String errorMessage = String.format("❌ 错误:%s\n%s", filepath, e.getMessage());
172 | messageCallback.updateMessage(errorMessage, javafx.scene.paint.Color.web("#6272A4"));
173 | results.add(errorMessage);
174 | }
175 | }
176 |
177 | // Webshell检测模式下不在这里添加统计信息,统计信息由UI层处理
178 | resultCallback.accept(String.join("\n", results));
179 | }
180 |
181 | private Map scanCodeFiles(File directory) {
182 | Map codeFiles = new HashMap<>();
183 | List allowedExt = new ArrayList<>(Arrays.asList(
184 | ".php", ".jsp", ".jspx", ".asp", ".aspx", ".js", ".html", ".py", ".java"
185 | ));
186 |
187 | if (!includeJsFiles) {
188 | allowedExt.remove(".js");
189 | allowedExt.remove(".html");
190 | }
191 |
192 | scanDirectory(directory, codeFiles, allowedExt);
193 | return codeFiles;
194 | }
195 |
196 | private void scanDirectory(File directory, Map codeFiles, List allowedExt) {
197 | File[] files = directory.listFiles();
198 | if (files != null) {
199 | for (File file : files) {
200 | if (file.isDirectory()) {
201 | scanDirectory(file, codeFiles, allowedExt);
202 | } else {
203 | String extension = getFileExtension(file);
204 | if (allowedExt.contains(extension.toLowerCase())) {
205 | try {
206 | String content = readFile(file);
207 | // 使用相对路径存储文件
208 | String relativePath = rootDirectory.toPath().relativize(file.toPath()).toString();
209 | codeFiles.put(relativePath, content);
210 | } catch (IOException e) {
211 | String relativePath = rootDirectory.toPath().relativize(file.toPath()).toString();
212 | codeFiles.put(relativePath, "无法读取文件内容");
213 | }
214 | }
215 | }
216 | }
217 | }
218 | }
219 |
220 | private String getFileExtension(File file) {
221 | String name = file.getName();
222 | int lastIndexOf = name.lastIndexOf(".");
223 | return lastIndexOf == -1 ? "" : name.substring(lastIndexOf);
224 | }
225 |
226 | private String readFile(File file) throws IOException {
227 | StringBuilder content = new StringBuilder();
228 | try (BufferedReader reader = new BufferedReader(
229 | new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
230 | String line;
231 | while ((line = reader.readLine()) != null) {
232 | content.append(line).append("\n");
233 | }
234 | }
235 | return content.toString();
236 | }
237 |
238 | private String createPrompt(String content) {
239 | if (isAuditMode) {
240 | return String.format("【强制指令】你是一个专业的安全审计AI,请按以下要求分析代码:\n\n" +
241 | "1. 漏洞分析流程:\n" +
242 | " 1.1 识别潜在风险点(SQL操作、文件操作、用户输入点、文件上传漏洞、CSRF、SSRF、XSS、RCE、OWASP top10等漏洞)\n" +
243 | " 1.2 验证漏洞可利用性\n" +
244 | " 1.3 按CVSS评分标准评估风险等级\n\n" +
245 | "2. 输出规则:\n" +
246 | " - 仅输出确认存在的高危/中危漏洞\n" +
247 | " - 使用严格格式:[风险等级] 类型 - 位置:行号 - 50字内描述\n" +
248 | " - 禁止解释漏洞原理\n" +
249 | " - 禁止给出修复建议\n" +
250 | " - 如果有可能,给出POC(HTTP请求数据包)\n\n" +
251 | "3. 输出示例(除此外不要有任何输出):\n" +
252 | " [高危] SQL注入 - user_login.php:32 - 未过滤的$_GET参数直接拼接SQL查询\n" +
253 | " [POC]\nPOST /login.php HTTP/1.1\n" +
254 | " Host: example.com\n" +
255 | " Content-Type: application/x-www-form-urlencoded\n" +
256 | " [中危] XSS - comment.jsp:15 - 未转义的userInput输出到HTML\n" +
257 | " [POC]\nPOST /login.php HTTP/1.1\n" +
258 | " Host: example.com\n" +
259 | " Content-Type: application/x-www-form-urlencoded\n\n" +
260 | "4. 当前代码(仅限分析):\n%s", content.substring(0, Math.min(content.length(), 3000)));
261 | } else {
262 | return String.format("【Webshell检测指令】请严格按以下步骤分析代码:\n\n" +
263 | "1. 检测要求: \n" +
264 | " 请分析以下文件内容是否为WebShell或内存马。要求:\n" +
265 | " 1. 检查PHP/JSP/ASP等WebShell特征(如加密函数、执行系统命令、文件操作)\n" +
266 | " 2. 识别内存马特征(如无文件落地、进程注入、异常网络连接)\n" +
267 | " 3. 分析代码中的可疑功能(如命令执行、文件上传、信息收集)\n" +
268 | " 4. 检查混淆编码、加密手段等规避技术\n\n" +
269 | "2. 判断规则:\n" +
270 | " - 仅当确认恶意性时报告\n" +
271 | " - 输出格式:🔴 [高危] Webshell - 文件名:行号 - 检测到[特征1+特征2+...]\n\n" +
272 | "3. 输出示例(严格按照此格式输出,不要有任何的补充,如果未检测到危险,则不输出,除此之外,不要有任何输出):\n" +
273 | " 🔴 [高危] Webshell - malicious.php:8 - 检测到[system执行+base64解码+错误抑制]\n\n" +
274 | "4. 待分析代码:\n%s", content.substring(0, Math.min(content.length(), 3000)));
275 | }
276 | }
277 |
278 | private String callOllamaAPI(String prompt) throws IOException {
279 | MediaType JSON = MediaType.get("application/json; charset=utf-8");
280 | Map requestMap = new HashMap<>();
281 | requestMap.put("model", ollamaModel);
282 | requestMap.put("prompt", prompt);
283 | requestMap.put("stream", false);
284 | RequestBody body = RequestBody.create(JSON, objectMapper.writeValueAsString(requestMap));
285 |
286 | Request request = new Request.Builder()
287 | .url(ollamaHost + "/api/generate")
288 | .post(body)
289 | .build();
290 |
291 | try (Response response = httpClient.newCall(request).execute()) {
292 | if (!response.isSuccessful()) throw new IOException("请求失败: " + response);
293 | JsonNode jsonResponse = objectMapper.readTree(response.body().string());
294 | return jsonResponse.get("response").asText();
295 | }
296 | }
297 |
298 | private String processResponse(String response) {
299 | return response.replaceAll(".*?", "");
300 | }
301 | }
--------------------------------------------------------------------------------
/src/main/java/com/cyberscanner/StatusBar.java:
--------------------------------------------------------------------------------
1 | package com.cyberscanner;
2 |
3 | import javafx.animation.KeyFrame;
4 | import javafx.animation.KeyValue;
5 | import javafx.animation.Timeline;
6 | import javafx.geometry.Insets;
7 | import javafx.scene.control.Label;
8 | import javafx.scene.control.ProgressBar;
9 | import javafx.scene.effect.Glow;
10 | import javafx.scene.layout.HBox;
11 | import javafx.scene.layout.Priority;
12 | import javafx.scene.paint.Color;
13 | import javafx.scene.text.Font;
14 | import javafx.util.Duration;
15 |
16 | public class StatusBar extends HBox {
17 | private final Label statusLabel;
18 | private final Label progressLabel;
19 | private final ProgressBar progressBar;
20 | private Timeline progressTimeline;
21 |
22 | public StatusBar() {
23 | setSpacing(10);
24 | setPadding(new Insets(5));
25 | getStyleClass().add("status-bar");
26 |
27 | // 创建状态文本标签
28 | statusLabel = new Label("就绪");
29 | statusLabel.setFont(Font.font("System", 14));
30 | statusLabel.setTextFill(Color.valueOf("#4d4dff"));
31 | statusLabel.getStyleClass().add("status-label");
32 | statusLabel.setWrapText(true);
33 | statusLabel.setMaxWidth(Double.MAX_VALUE);
34 |
35 | // 添加发光效果
36 | Glow glow = new Glow(0.6);
37 | statusLabel.setEffect(glow);
38 |
39 | // 创建进度条
40 | progressBar = new ProgressBar();
41 | progressBar.setProgress(0);
42 | progressBar.setPrefWidth(200);
43 | progressBar.getStyleClass().add("status-progress");
44 | progressBar.setVisible(false);
45 |
46 | // 创建进度百分比标签
47 | progressLabel = new Label("0%");
48 | progressLabel.setFont(Font.font("System", 12));
49 | progressLabel.setTextFill(Color.valueOf("#4d4dff"));
50 | progressLabel.setVisible(false);
51 |
52 | // 设置布局
53 | getChildren().addAll(statusLabel, progressBar, progressLabel);
54 | HBox.setHgrow(statusLabel, Priority.ALWAYS);
55 | }
56 |
57 | public void setStatus(String text) {
58 | statusLabel.setText(text);
59 | }
60 |
61 | public void setProgress(double progress) {
62 | // 确保进度条和百分比标签可见
63 | if (!progressBar.isVisible()) {
64 | progressBar.setVisible(true);
65 | progressLabel.setVisible(true);
66 | }
67 |
68 | // 取消之前的动画(如果存在)
69 | if (progressTimeline != null) {
70 | progressTimeline.stop();
71 | }
72 |
73 | // 创建平滑动画
74 | progressTimeline = new Timeline(
75 | new KeyFrame(Duration.ZERO,
76 | new KeyValue(progressBar.progressProperty(), progressBar.getProgress())),
77 | new KeyFrame(Duration.millis(500),
78 | new KeyValue(progressBar.progressProperty(), progress))
79 | );
80 |
81 | progressTimeline.setOnFinished(event -> {
82 | // 更新百分比标签
83 | int percentage = (int) (progress * 100);
84 | progressLabel.setText(percentage + "%");
85 | });
86 |
87 | progressTimeline.play();
88 | }
89 |
90 | public void reset() {
91 | statusLabel.setText("就绪");
92 | progressBar.setProgress(0);
93 | progressLabel.setText("0%");
94 | progressBar.setVisible(false);
95 | progressLabel.setVisible(false);
96 | }
97 | }
--------------------------------------------------------------------------------
/src/main/resources/config.yaml:
--------------------------------------------------------------------------------
1 | # API配置
2 | api:
3 | type: ollama # 可选值: "deepseek" 或 "ollama"
4 |
5 | # DeepSeek API配置
6 | deepseek:
7 | # 官方默认API地址: "https://api.deepseek.com/v1/chat/completions"
8 | # 硅基流动:https://api.siliconflow.cn/v1/chat/completions
9 | url: ""
10 | api_key: ""
11 | # DeepSeek模型名称,官方默认模型: "deepseek-chat"
12 | # 硅基流动:deepseek-ai/DeepSeek-V3
13 | model: ""
14 |
15 | # Ollama API配置
16 | ollama:
17 | url: "http://x.x.x.x/api/chat" # Ollama API地址
18 | model: "qwen2.5:7b" # Ollama模型名称
19 |
20 | # 主题配色方案
21 | themes:
22 | dark:
23 | main_bg: "#1a1a2e"
24 | secondary_bg: "#16213e"
25 | text_color: "#e4e4e4"
26 | accent_color: "#4d4dff"
27 | border_color: "#7b2cbf"
28 | button_hover: "#00b4d8"
29 | button_pressed: "#0096c7"
30 | gradient_start: "#2b2b4b"
31 | gradient_end: "#1a1a2e"
32 | neon_glow: "#4d4dff"
33 | highlight: "#7b2cbf"
34 |
35 | light:
36 | main_bg: "#f5f5f5"
37 | secondary_bg: "#ffffff"
38 | text_color: "#333333"
39 | accent_color: "#2196f3"
40 | border_color: "#e0e0e0"
41 | button_hover: "#1976d2"
42 | button_pressed: "#1565c0"
--------------------------------------------------------------------------------
/src/main/resources/styles/cyber-theme.css:
--------------------------------------------------------------------------------
1 | .main-layout {
2 | -fx-background-color: #282a36;
3 | -fx-text-fill: #f8f8f2;
4 | }
5 |
6 | .left-panel {
7 | -fx-background-color: #44475a;
8 | -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.3), 10, 0, 0, 0);
9 | -fx-min-width: 300;
10 | -fx-pref-width: 300;
11 | }
12 |
13 | .cyber-button, .scan-button {
14 | -fx-background-color: #6272a4;
15 | -fx-text-fill: #f8f8f2;
16 | -fx-font-size: 14px;
17 | -fx-padding: 10px 20px;
18 | -fx-background-radius: 5px;
19 | }
20 |
21 | .cyber-button:hover, .scan-button:hover {
22 | -fx-background-color: #50fa7b;
23 | -fx-text-fill: #282a36;
24 | }
25 |
26 | .cyber-button:hover {
27 | -fx-background-color: #1a1a2e;
28 | -fx-border-color: #00ffff;
29 | -fx-effect: dropshadow(gaussian, #00ffff88, 20, 0, 0, 0);
30 | }
31 |
32 | .path-label {
33 | -fx-text-fill: #cc66ff;
34 | -fx-font-size: 10pt;
35 | -fx-font-family: 'Consolas';
36 | -fx-padding: 5;
37 | }
38 |
39 | .mode-box {
40 | -fx-spacing: 5;
41 | -fx-padding: 10;
42 | -fx-border-color: #ff33ff;
43 | -fx-border-width: 1;
44 | -fx-border-radius: 5;
45 | -fx-effect: dropshadow(gaussian, #ff33ff44, 10, 0, 0, 0);
46 | }
47 |
48 | .mode-label {
49 | -fx-text-fill: #cc66ff;
50 | -fx-font-size: 12pt;
51 | -fx-font-family: 'Consolas';
52 | -fx-font-weight: bold;
53 | }
54 |
55 | .jfx-radio-button {
56 | -fx-text-fill: #cc66ff;
57 | -fx-padding: 8;
58 | }
59 |
60 | .jfx-radio-button .radio {
61 | -fx-background-color: #2b0052;
62 | -fx-border-color: #ff33ff;
63 | -fx-border-width: 2;
64 | }
65 |
66 | .jfx-radio-button:selected .radio .dot {
67 | -fx-background-color: #ff33ff;
68 | }
69 |
70 | .jfx-check-box {
71 | -fx-text-fill: #cc66ff;
72 | -fx-padding: 8;
73 | }
74 |
75 | .jfx-check-box .box {
76 | -fx-background-color: #2b0052;
77 | -fx-border-color: #ff33ff;
78 | -fx-border-width: 2;
79 | }
80 |
81 | .jfx-check-box:selected .box .mark {
82 | -fx-background-color: #ff33ff;
83 | }
84 |
85 | .file-tree {
86 | -fx-background-color: #44475a;
87 | }
88 |
89 | .file-tree .tree-cell {
90 | -fx-background-color: #44475a;
91 | -fx-text-fill: #f8f8f2;
92 | }
93 |
94 | .file-tree .tree-cell:selected {
95 | -fx-background-color: #6272a4;
96 | }
97 |
98 | .file-tree .tree-cell {
99 | -fx-background-color: transparent;
100 | -fx-text-fill: #cc66ff;
101 | -fx-padding: 5;
102 | }
103 |
104 | .file-tree .tree-cell:hover {
105 | -fx-background-color: #2b0052;
106 | }
107 |
108 | .scan-button {
109 | -fx-background-color: #4d0099;
110 | -fx-text-fill: #ff33ff;
111 | -fx-border-color: #ff33ff;
112 | -fx-border-width: 2;
113 | -fx-padding: 15;
114 | -fx-font-size: 16pt;
115 | -fx-font-family: 'Consolas';
116 | -fx-font-weight: bold;
117 | -fx-border-radius: 5;
118 | -fx-background-radius: 5;
119 | -fx-cursor: hand;
120 | -fx-effect: dropshadow(gaussian, #ff33ff44, 20, 0, 0, 0);
121 | }
122 |
123 | .scan-button:hover {
124 | -fx-background-color: #1a1a2e;
125 | -fx-effect: dropshadow(gaussian, #00ffff88, 25, 0, 0, 0);
126 | -fx-text-fill: #00ffff;
127 | }
128 |
129 | .scan-button:disabled {
130 | -fx-background-color: #2b0052;
131 | -fx-text-fill: #9933ff;
132 | -fx-border-color: #4d0099;
133 | -fx-effect: none;
134 | }
135 |
136 | .result-display {
137 | -fx-font-family: "JetBrains Mono", "Consolas", monospace;
138 | -fx-font-size: 14px;
139 | -fx-background-color: #282a36;
140 | -fx-text-fill: #f8f8f2;
141 | -fx-padding: 10px;
142 | }
143 |
144 | .result-display .content {
145 | -fx-background-color: #282a36;
146 | }
147 |
148 | .status-bar {
149 | -fx-padding: 5px;
150 | -fx-background-color: #44475a;
151 | -fx-text-fill: #f8f8f2;
152 | }
153 |
154 | .progress-bar {
155 | -fx-accent: #50fa7b;
156 | }
157 |
158 | .progress-bar .track {
159 | -fx-background-color: #44475a;
160 | }
161 |
162 | .progress-bar .track {
163 | -fx-background-color: #2b0052;
164 | }
165 |
166 | .progress-bar .bar {
167 | -fx-background-insets: 0;
168 | -fx-background-radius: 0;
169 | -fx-effect: dropshadow(gaussian, #ff33ff44, 10, 0, 0, 0);
170 | }
171 |
172 | /* 滚动条样式 */
173 | .scroll-bar:vertical,
174 | .scroll-bar:horizontal {
175 | -fx-background-color: #1a0033;
176 | }
177 |
178 | .scroll-bar:vertical .thumb,
179 | .scroll-bar:horizontal .thumb {
180 | -fx-background-color: #4d0099;
181 | -fx-background-radius: 0;
182 | }
183 |
184 | .scroll-bar:vertical .thumb:hover,
185 | .scroll-bar:horizontal .thumb:hover {
186 | -fx-background-color: #6600cc;
187 | }
188 |
189 | .scroll-bar .increment-button,
190 | .scroll-bar .decrement-button {
191 | -fx-background-color: #2b0052;
192 | -fx-border-color: #4d0099;
193 | }
194 |
195 | .scroll-bar .increment-button:hover,
196 | .scroll-bar .decrement-button:hover {
197 | -fx-background-color: #4d0099;
198 | }
199 |
200 | /* 动画效果 */
201 | .cyber-glow {
202 | -fx-effect: dropshadow(gaussian, #ff33ff44, 20, 0, 0, 0);
203 | -fx-transition: all 0.3s ease;
204 | }
205 |
206 | .cyber-glow:hover {
207 | -fx-effect: dropshadow(gaussian, #ff33ff88, 25, 0, 0, 0);
208 | }
--------------------------------------------------------------------------------