2025-02-04 17:49:47 +08:00
|
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
import json
|
|
|
|
|
import requests
|
|
|
|
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
|
|
|
|
from PyQt5.QtCore import QThread, pyqtSignal
|
|
|
|
|
from config import OLLAMA_API_URL, OLLAMA_MODEL # 用户自定义配置
|
|
|
|
|
|
|
|
|
|
class CyberTextEdit(QtWidgets.QTextEdit):
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
self.setStyleSheet("""
|
|
|
|
|
QTextEdit {
|
|
|
|
|
background-color: #001a1a;
|
|
|
|
|
color: #00ff00;
|
|
|
|
|
border: 2px solid #00ffff;
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
font-family: 'Consolas';
|
|
|
|
|
font-size: 12pt;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
class HackerWorker(QThread):
|
|
|
|
|
analysis_complete = pyqtSignal(str)
|
|
|
|
|
progress_update = pyqtSignal(str)
|
|
|
|
|
|
|
|
|
|
def __init__(self, files_content):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.files_content = files_content
|
|
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
|
full_report = []
|
|
|
|
|
for filepath, content in self.files_content.items():
|
|
|
|
|
self.progress_update.emit(f"🔍 Analyzing {os.path.basename(filepath)}...")
|
|
|
|
|
|
|
|
|
|
prompt = f"""【强制指令】你是一个专业的安全审计AI,请按以下要求分析代码:
|
|
|
|
|
|
|
|
|
|
1. 漏洞分析流程:
|
|
|
|
|
1.1 识别潜在风险点(SQL操作、文件操作、用户输入点、文件上传漏洞、CSRF、SSRF、XSS、RCE、OWASP top10等漏洞)
|
|
|
|
|
1.2 验证漏洞可利用性
|
|
|
|
|
1.3 按CVSS评分标准评估风险等级
|
|
|
|
|
|
|
|
|
|
2. 输出规则:
|
|
|
|
|
- 仅输出确认存在的高危/中危漏洞
|
|
|
|
|
- 使用严格格式:[风险等级] 类型 - 位置:行号 - 50字内描述
|
|
|
|
|
- 禁止解释漏洞原理
|
|
|
|
|
- 禁止给出修复建议
|
2025-02-10 20:22:23 +08:00
|
|
|
|
- 如果有可能,给出POC(HTTP请求数据包)
|
2025-02-04 17:49:47 +08:00
|
|
|
|
|
|
|
|
|
3. 输出示例(除此外不要有任何输出):
|
|
|
|
|
[高危] SQL注入 - user_login.php:32 - 未过滤的$_GET参数直接拼接SQL查询
|
2025-02-10 20:22:23 +08:00
|
|
|
|
[POC]POST /login.php HTTP/1.1
|
|
|
|
|
Host: example.com
|
|
|
|
|
Content-Type: application/x-www-form-urlencoded
|
2025-02-04 17:49:47 +08:00
|
|
|
|
[中危] XSS - comment.jsp:15 - 未转义的userInput输出到HTML
|
2025-02-10 20:22:23 +08:00
|
|
|
|
[POC]POST /login.php HTTP/1.1
|
|
|
|
|
Host: example.com
|
|
|
|
|
Content-Type: application/x-www-form-urlencoded
|
2025-02-04 17:49:47 +08:00
|
|
|
|
|
|
|
|
|
4. 当前代码(仅限分析):
|
|
|
|
|
{content[:3000]}"""
|
|
|
|
|
try:
|
|
|
|
|
response = requests.post(
|
|
|
|
|
f"{OLLAMA_HOST}/api/generate",
|
|
|
|
|
json={
|
|
|
|
|
"model": OLLAMA_MODEL,
|
|
|
|
|
"prompt": prompt,
|
|
|
|
|
"stream": False
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
result = json.loads(response.text)["response"]
|
|
|
|
|
result = re.sub(r'<think>.*?</think>', '', result, flags=re.DOTALL)
|
|
|
|
|
full_report.append(f"📄 文件:{filepath}\n{result}\n{'━'*50}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
full_report.append(f"❌ 错误:处理文件 {filepath} 时发生错误\n{str(e)}")
|
|
|
|
|
|
|
|
|
|
self.analysis_complete.emit("\n".join(full_report))
|
|
|
|
|
|
|
|
|
|
class WebshellWorker(QThread):
|
|
|
|
|
detection_complete = pyqtSignal(str)
|
|
|
|
|
progress_update = pyqtSignal(str)
|
|
|
|
|
|
|
|
|
|
def __init__(self, files_content):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.files_content = files_content
|
|
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
|
detection_results = []
|
|
|
|
|
for filepath, content in self.files_content.items():
|
|
|
|
|
self.progress_update.emit(f"🕵️ 扫描 {os.path.basename(filepath)}...")
|
|
|
|
|
|
|
|
|
|
prompt = f"""【Webshell检测指令】请严格按以下步骤分析代码:
|
|
|
|
|
|
|
|
|
|
1. 检测要求:
|
|
|
|
|
请分析以下文件内容是否为WebShell或内存马。要求:
|
|
|
|
|
1. 检查PHP/JSP/ASP等WebShell特征(如加密函数、执行系统命令、文件操作)
|
|
|
|
|
2. 识别内存马特征(如无文件落地、进程注入、异常网络连接)
|
|
|
|
|
3. 分析代码中的可疑功能(如命令执行、文件上传、信息收集)
|
|
|
|
|
4. 检查混淆编码、加密手段等规避技术
|
|
|
|
|
|
|
|
|
|
2. 判断规则:
|
|
|
|
|
- 仅当确认恶意性时报告
|
|
|
|
|
- 输出格式:🔴 [高危] Webshell - 文件名:行号 - 检测到[特征1+特征2+...]
|
|
|
|
|
|
|
|
|
|
3. 输出示例(严格按照此格式输出,不要有任何的补充,如果未检测到危险,则不输出,除此之外,不要有任何输出):
|
|
|
|
|
🔴 [高危] Webshell - malicious.php:8 - 检测到[system执行+base64解码+错误抑制]
|
|
|
|
|
|
|
|
|
|
4. 待分析代码:
|
|
|
|
|
{content[:3000]}"""
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
response = requests.post(
|
|
|
|
|
f"{OLLAMA_HOST}/api/generate",
|
|
|
|
|
json={
|
|
|
|
|
"model": OLLAMA_MODEL,
|
|
|
|
|
"prompt": prompt,
|
|
|
|
|
"stream": False
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
result = json.loads(response.text)["response"]
|
|
|
|
|
result = re.sub(r'<think>.*?</think>', '', result, flags=re.DOTALL)
|
|
|
|
|
detection_results.append(f"📁 {filepath}\n{result}\n{'━'*50}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
detection_results.append(f"❌ 错误:{filepath}\n{str(e)}")
|
|
|
|
|
|
|
|
|
|
self.detection_complete.emit("\n".join(detection_results))
|
|
|
|
|
|
|
|
|
|
class CyberScanner(QtWidgets.QMainWindow):
|
|
|
|
|
def __init__(self):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.setWindowTitle("AI安全审计系统 BY:公众号_知攻善防实验室 ChinaRan404 ")
|
|
|
|
|
self.setGeometry(100, 100, 1280, 720)
|
|
|
|
|
self.setup_ui()
|
|
|
|
|
self.files_content = {}
|
|
|
|
|
self.scan_thread = None
|
|
|
|
|
|
|
|
|
|
def setup_ui(self):
|
|
|
|
|
main_widget = QtWidgets.QWidget()
|
|
|
|
|
self.setCentralWidget(main_widget)
|
|
|
|
|
layout = QtWidgets.QHBoxLayout(main_widget)
|
|
|
|
|
|
|
|
|
|
# 左侧面板
|
|
|
|
|
left_panel = QtWidgets.QFrame()
|
|
|
|
|
left_panel.setStyleSheet("background-color: #000d1a; border-right: 2px solid #00ffff;")
|
|
|
|
|
left_layout = QtWidgets.QVBoxLayout(left_panel)
|
|
|
|
|
|
|
|
|
|
# 目录选择按钮
|
|
|
|
|
self.btn_select = QtWidgets.QPushButton("📁 激活数据源")
|
|
|
|
|
self.btn_select.setStyleSheet("""
|
|
|
|
|
QPushButton {
|
|
|
|
|
background-color: #002b2b;
|
|
|
|
|
color: #00ffff;
|
|
|
|
|
border: 2px solid #008080;
|
|
|
|
|
padding: 12px;
|
|
|
|
|
font-size: 14pt;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
}
|
|
|
|
|
QPushButton:hover {
|
|
|
|
|
background-color: #004d4d;
|
|
|
|
|
border-color: #00ffff;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
self.btn_select.clicked.connect(self.select_directory)
|
|
|
|
|
left_layout.addWidget(self.btn_select)
|
|
|
|
|
|
|
|
|
|
# 路径显示
|
|
|
|
|
self.lbl_path = QtWidgets.QLabel("未选择数据源")
|
|
|
|
|
self.lbl_path.setStyleSheet("color: #00ff00; font-size: 10pt; padding: 5px;")
|
|
|
|
|
left_layout.addWidget(self.lbl_path)
|
|
|
|
|
|
|
|
|
|
# 模式选择
|
|
|
|
|
mode_group = QtWidgets.QGroupBox("🔧 检测模式")
|
|
|
|
|
mode_group.setStyleSheet("""
|
|
|
|
|
QGroupBox {
|
|
|
|
|
color: #00ff00;
|
|
|
|
|
border: 1px solid #00ffff;
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
font-size: 12pt;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
mode_layout = QtWidgets.QVBoxLayout()
|
|
|
|
|
self.radio_audit = QtWidgets.QRadioButton("代码安全审计")
|
|
|
|
|
self.radio_webshell = QtWidgets.QRadioButton("Webshell检测")
|
|
|
|
|
self.radio_audit.setChecked(True)
|
|
|
|
|
for rb in [self.radio_audit, self.radio_webshell]:
|
|
|
|
|
rb.setStyleSheet("""
|
|
|
|
|
QRadioButton { color: #00ff00; padding: 8px; }
|
|
|
|
|
QRadioButton::indicator { width: 20px; height: 20px; }
|
|
|
|
|
""")
|
|
|
|
|
mode_layout.addWidget(rb)
|
|
|
|
|
mode_group.setLayout(mode_layout)
|
|
|
|
|
left_layout.addWidget(mode_group)
|
|
|
|
|
|
|
|
|
|
# 文件树
|
|
|
|
|
self.file_tree = QtWidgets.QTreeView()
|
|
|
|
|
self.file_model = QtWidgets.QFileSystemModel()
|
|
|
|
|
self.file_model.setRootPath("")
|
|
|
|
|
self.file_tree.setModel(self.file_model)
|
|
|
|
|
self.file_tree.setStyleSheet("""
|
|
|
|
|
QTreeView {
|
|
|
|
|
background-color: #001a1a;
|
|
|
|
|
color: #00ff00;
|
|
|
|
|
border: 1px solid #008080;
|
|
|
|
|
font-family: 'Consolas';
|
|
|
|
|
}
|
|
|
|
|
QTreeView::item:hover { background-color: #003333; }
|
|
|
|
|
""")
|
|
|
|
|
left_layout.addWidget(self.file_tree)
|
|
|
|
|
|
|
|
|
|
# 扫描按钮
|
|
|
|
|
self.btn_scan = QtWidgets.QPushButton("🚨 启动扫描协议")
|
|
|
|
|
self.btn_scan.setStyleSheet("""
|
|
|
|
|
QPushButton {
|
|
|
|
|
background-color: #004d4d;
|
|
|
|
|
color: #00ffff;
|
|
|
|
|
border: 2px solid #00ffff;
|
|
|
|
|
padding: 15px;
|
|
|
|
|
font-size: 16pt;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
}
|
|
|
|
|
QPushButton:disabled {
|
|
|
|
|
background-color: #002b2b;
|
|
|
|
|
color: #008080;
|
|
|
|
|
border-color: #004d4d;
|
|
|
|
|
}
|
|
|
|
|
QPushButton:hover { background-color: #006666; }
|
|
|
|
|
""")
|
|
|
|
|
self.btn_scan.clicked.connect(self.start_scan)
|
|
|
|
|
self.btn_scan.setEnabled(False)
|
|
|
|
|
left_layout.addWidget(self.btn_scan)
|
|
|
|
|
|
|
|
|
|
# 右侧显示区
|
|
|
|
|
self.result_display = CyberTextEdit()
|
|
|
|
|
self.result_display.setAcceptRichText(True)
|
|
|
|
|
|
|
|
|
|
layout.addWidget(left_panel, 1)
|
|
|
|
|
layout.addWidget(self.result_display, 2)
|
|
|
|
|
|
|
|
|
|
# 状态栏
|
|
|
|
|
self.status_bar = QtWidgets.QStatusBar()
|
|
|
|
|
self.setStatusBar(self.status_bar)
|
|
|
|
|
self.status_bar.setStyleSheet("""
|
|
|
|
|
QStatusBar {
|
|
|
|
|
background-color: #000d1a;
|
|
|
|
|
color: #00ff00;
|
|
|
|
|
border-top: 1px solid #00ffff;
|
|
|
|
|
font-family: 'Consolas';
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
def select_directory(self):
|
|
|
|
|
directory = QtWidgets.QFileDialog.getExistingDirectory(
|
|
|
|
|
self,
|
|
|
|
|
"选择代码矩阵接入点",
|
|
|
|
|
"",
|
|
|
|
|
QtWidgets.QFileDialog.ShowDirsOnly
|
|
|
|
|
)
|
|
|
|
|
if directory:
|
|
|
|
|
self.lbl_path.setText(f"📂 数据源:{directory}")
|
|
|
|
|
self.file_tree.setRootIndex(self.file_model.index(directory))
|
|
|
|
|
self.btn_scan.setEnabled(True)
|
|
|
|
|
self.status_bar.showMessage("✅ 数据源接入成功")
|
|
|
|
|
|
|
|
|
|
def start_scan(self):
|
|
|
|
|
root_index = self.file_tree.rootIndex()
|
|
|
|
|
if not root_index.isValid():
|
|
|
|
|
QtWidgets.QMessageBox.warning(self, "警告", "请先选择代码目录!")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
root_path = self.file_model.filePath(root_index)
|
|
|
|
|
self.files_content = self.scan_code_files(root_path)
|
|
|
|
|
|
|
|
|
|
if self.radio_audit.isChecked():
|
|
|
|
|
worker = HackerWorker(self.files_content)
|
|
|
|
|
init_msg = "🚀 启动深度代码分析协议..."
|
|
|
|
|
complete_signal = worker.analysis_complete
|
|
|
|
|
else:
|
|
|
|
|
worker = WebshellWorker(self.files_content)
|
|
|
|
|
init_msg = "🕵️ 启动Webshell检测协议..."
|
|
|
|
|
complete_signal = worker.detection_complete
|
|
|
|
|
|
|
|
|
|
self.scan_thread = worker
|
|
|
|
|
self.scan_thread.progress_update.connect(self.update_status)
|
|
|
|
|
complete_signal.connect(self.show_results)
|
|
|
|
|
self.scan_thread.start()
|
|
|
|
|
|
|
|
|
|
self.btn_scan.setEnabled(False)
|
|
|
|
|
self.result_display.setText(f"{init_msg}\n" + "▮"*50 + "\n")
|
|
|
|
|
|
|
|
|
|
def scan_code_files(self, directory):
|
|
|
|
|
allowed_ext = ['.php', '.jsp', '.asp', '.js', '.html', '.py', '.java']
|
|
|
|
|
code_files = {}
|
|
|
|
|
|
|
|
|
|
for root, _, files in os.walk(directory):
|
|
|
|
|
for file in files:
|
|
|
|
|
if os.path.splitext(file)[1].lower() in allowed_ext:
|
|
|
|
|
path = os.path.join(root, file)
|
|
|
|
|
try:
|
|
|
|
|
with open(path, 'r', encoding='utf-8') as f:
|
|
|
|
|
code_files[path] = f.read()
|
|
|
|
|
except:
|
|
|
|
|
code_files[path] = "无法读取文件内容"
|
|
|
|
|
return code_files
|
|
|
|
|
|
|
|
|
|
def update_status(self, message):
|
|
|
|
|
self.status_bar.showMessage(message)
|
|
|
|
|
self.result_display.append(f"⚡ {message}")
|
|
|
|
|
|
|
|
|
|
def show_results(self, report):
|
|
|
|
|
self.btn_scan.setEnabled(True)
|
|
|
|
|
|
|
|
|
|
if self.radio_webshell.isChecked():
|
|
|
|
|
self.result_display.append("\n🔍 Webshell检测完成!结果如下:\n")
|
|
|
|
|
report = re.sub(r'🔴 \[高危\]', '🔴 [高危]', report)
|
|
|
|
|
report = re.sub(r'✅ \[安全\]', '✅ [安全]', report)
|
|
|
|
|
else:
|
|
|
|
|
self.result_display.append("\n🔥 代码审计完成!发现以下安全漏洞:\n")
|
|
|
|
|
report = re.sub(r'\[高危\]', '[高危]', report)
|
|
|
|
|
report = re.sub(r'\[中危\]', '[中危]', report)
|
|
|
|
|
|
|
|
|
|
self.result_display.append(report)
|
|
|
|
|
self.status_bar.showMessage("✅ 扫描完成")
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
# 保持源文本的核心内容不变
|
|
|
|
|
|
|
|
|
|
OLLAMA_HOST = OLLAMA_API_URL.split('/api')[0]
|
|
|
|
|
|
|
|
|
|
app = QtWidgets.QApplication([])
|
|
|
|
|
app.setStyle('Fusion')
|
|
|
|
|
|
|
|
|
|
font = QtGui.QFont("Consolas", 10)
|
|
|
|
|
app.setFont(font)
|
|
|
|
|
|
|
|
|
|
window = CyberScanner()
|
|
|
|
|
window.show()
|
|
|
|
|
app.exec_()
|