package services import ( "context" "crypto/tls" "encoding/base64" "fmt" "io" "net/http" "strings" "time" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/plugins" ) type ElasticsearchPlugin struct { plugins.BasePlugin } func NewElasticsearchPlugin() *ElasticsearchPlugin { return &ElasticsearchPlugin{ BasePlugin: plugins.NewBasePlugin("elasticsearch"), } } func (p *ElasticsearchPlugin) 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) } credentials := GenerateCredentials("elasticsearch") if len(credentials) == 0 { return &ScanResult{ Success: false, Service: "elasticsearch", Error: fmt.Errorf("没有可用的测试凭据"), } } for _, cred := range credentials { if p.testCredential(ctx, info, cred) { common.LogSuccess(fmt.Sprintf("Elasticsearch %s %s:%s", target, cred.Username, cred.Password)) return &ScanResult{ Success: true, Service: "elasticsearch", Username: cred.Username, Password: cred.Password, } } } return &ScanResult{ Success: false, Service: "elasticsearch", Error: fmt.Errorf("未发现弱密码"), } } func (p *ElasticsearchPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool { // 检查发包限制 if canSend, reason := common.CanSendPacket(); !canSend { common.LogError(fmt.Sprintf("Elasticsearch连接 %s:%s 受限: %s", info.Host, info.Ports, reason)) return false } client := &http.Client{ Timeout: time.Duration(common.Timeout) * time.Second, Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, } // 构建URL protocol := "http" if info.Ports == "9443" { protocol = "https" } url := fmt.Sprintf("%s://%s:%s/", protocol, info.Host, info.Ports) req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return false } if cred.Username != "" || cred.Password != "" { auth := base64.StdEncoding.EncodeToString([]byte(cred.Username + ":" + cred.Password)) req.Header.Set("Authorization", "Basic "+auth) } resp, err := client.Do(req) if err != nil { common.IncrementTCPFailedPacketCount() return false } common.IncrementTCPSuccessPacketCount() defer resp.Body.Close() if resp.StatusCode == 200 { body, err := io.ReadAll(resp.Body) if err != nil { return false } bodyStr := string(body) return strings.Contains(bodyStr, "elasticsearch") || strings.Contains(bodyStr, "cluster_name") } return false } func (p *ElasticsearchPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { // 检查发包限制 if canSend, reason := common.CanSendPacket(); !canSend { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) common.LogError(fmt.Sprintf("Elasticsearch识别 %s 受限: %s", target, reason)) return &ScanResult{ Success: false, Service: "elasticsearch", Error: fmt.Errorf("发包受限: %s", reason), } } if p.testCredential(ctx, info, Credential{Username: "", Password: ""}) { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) banner := "Elasticsearch" common.LogSuccess(fmt.Sprintf("Elasticsearch %s %s", target, banner)) return &ScanResult{ Success: true, Service: "elasticsearch", Banner: banner, } } return &ScanResult{ Success: false, Service: "elasticsearch", Error: fmt.Errorf("无法识别为Elasticsearch服务"), } } func init() { // 使用高效注册方式:直接传递端口信息,避免实例创建 RegisterPluginWithPorts("elasticsearch", func() Plugin { return NewElasticsearchPlugin() }, []int{9200, 9300}) }