fscan/plugins/services/memcached.go
ZacharyZcR e082e2bb59 refactor: 重组插件目录结构,提升管理直观性
将所有服务插件移动到plugins/services/目录下,使目录结构更加清晰直观:
• 创建plugins/services/目录统一管理服务扫描插件
• 添加init.go提供类型别名和函数导出
• 更新main.go导入路径
• 所有20个服务插件功能验证正常

新的目录结构更便于插件管理和维护。
2025-08-26 00:02:13 +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 services
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()
})
}