fscan/plugins/services/neo4j.go
ZacharyZcR 6cf5719e8a refactor: 彻底清理插件系统,消除虚假利用功能
- 删除整个legacy插件系统(7794行代码)
- 完成所有插件向单文件架构迁移
- 移除19个插件的虚假Exploit功能,只保留真实利用:
  * Redis: 文件写入、SSH密钥注入、计划任务
  * SSH: 命令执行
  * MS17010: EternalBlue漏洞利用
- 统一插件接口,简化架构复杂度
- 清理临时文件和备份文件

重构效果:
- 代码行数: -7794行
- 插件文件数: 从3文件架构→单文件架构
- 真实利用插件: 从22个→3个
- 架构复杂度: 大幅简化
2025-08-26 11:43:48 +08:00

458 lines
11 KiB
Go

package services
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
)
// Neo4jPlugin Neo4j图数据库扫描和利用插件 - 包含图数据查询利用功能
type Neo4jPlugin struct {
name string
ports []int
}
// NewNeo4jPlugin 创建Neo4j插件
func NewNeo4jPlugin() *Neo4jPlugin {
return &Neo4jPlugin{
name: "neo4j",
ports: []int{7474, 7687, 7473}, // Neo4j HTTP、Bolt、HTTPS端口
}
}
// GetName 实现Plugin接口
func (p *Neo4jPlugin) GetName() string {
return p.name
}
// GetPorts 实现Plugin接口
func (p *Neo4jPlugin) GetPorts() []int {
return p.ports
}
// Scan 执行Neo4j扫描 - 未授权访问和弱密码检测
func (p *Neo4jPlugin) 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)
}
// 首先检查未授权访问
if result := p.testUnauthorizedAccess(ctx, info); result != nil && result.Success {
common.LogSuccess(i18n.GetText("neo4j_unauth_success", target))
return result
}
// 生成测试凭据
credentials := GenerateCredentials("neo4j")
if len(credentials) == 0 {
// Neo4j默认凭据
credentials = []Credential{
{Username: "neo4j", Password: "neo4j"},
{Username: "neo4j", Password: "admin"},
{Username: "neo4j", Password: "password"},
{Username: "neo4j", Password: "123456"},
{Username: "admin", Password: "admin"},
{Username: "admin", Password: "neo4j"},
}
}
// 逐个测试凭据
for _, cred := range credentials {
// 检查Context是否被取消
select {
case <-ctx.Done():
return &ScanResult{
Success: false,
Service: "neo4j",
Error: ctx.Err(),
}
default:
}
// 测试凭据
if p.testCredential(ctx, info, cred) {
// Neo4j认证成功
common.LogSuccess(i18n.GetText("neo4j_scan_success", target, cred.Username, cred.Password))
return &ScanResult{
Success: true,
Service: "neo4j",
Username: cred.Username,
Password: cred.Password,
}
}
}
// 所有凭据都失败
return &ScanResult{
Success: false,
Service: "neo4j",
Error: fmt.Errorf("未发现弱密码或未授权访问"),
}
}
// testUnauthorizedAccess 测试未授权访问
func (p *Neo4jPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo) *ScanResult {
// 尝试无认证访问
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.Timeout) * time.Second,
}
// 检查是否可以直接访问数据库
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/db/data/", nil)
if err != nil {
return nil
}
resp, err := client.Do(req)
if err != nil {
return nil
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
return &ScanResult{
Success: true,
Service: "neo4j",
Banner: "未授权访问",
}
}
return nil
}
// testCredential 测试单个凭据
func (p *Neo4jPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.Timeout) * time.Second,
}
// 尝试认证
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/user/neo4j", nil)
if err != nil {
return false
}
req.SetBasicAuth(cred.Username, cred.Password)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return false
}
defer resp.Body.Close()
return resp.StatusCode == 200
}
// executeQuery 执行Cypher查询
func (p *Neo4jPlugin) executeQuery(ctx context.Context, info *common.HostInfo, creds Credential, query string) (map[string]interface{}, error) {
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.Timeout) * time.Second,
}
// 构建查询请求
queryData := map[string]interface{}{
"statements": []map[string]interface{}{
{
"statement": query,
},
},
}
jsonData, err := json.Marshal(queryData)
if err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(ctx, "POST", baseURL+"/db/data/transaction/commit", strings.NewReader(string(jsonData)))
if err != nil {
return nil, err
}
req.SetBasicAuth(creds.Username, creds.Password)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("查询失败,状态码: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var result map[string]interface{}
err = json.Unmarshal(body, &result)
if err != nil {
return nil, err
}
return result, nil
}
// getServerInfo 获取服务器信息
func (p *Neo4jPlugin) getServerInfo(ctx context.Context, info *common.HostInfo, creds Credential) string {
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.Timeout) * time.Second,
}
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/db/data/", nil)
if err != nil {
return ""
}
req.SetBasicAuth(creds.Username, creds.Password)
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return ""
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return ""
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return ""
}
var serverInfo map[string]interface{}
err = json.Unmarshal(body, &serverInfo)
if err != nil {
return ""
}
var info_str strings.Builder
if version, ok := serverInfo["neo4j_version"]; ok {
info_str.WriteString(fmt.Sprintf("Neo4j版本: %v\n", version))
}
if extensions, ok := serverInfo["extensions"]; ok {
info_str.WriteString(fmt.Sprintf("扩展信息: %v\n", extensions))
}
return info_str.String()
}
// getDatabaseStats 获取数据库统计信息
func (p *Neo4jPlugin) getDatabaseStats(ctx context.Context, info *common.HostInfo, creds Credential) string {
// 执行统计查询
result, err := p.executeQuery(ctx, info, creds, "MATCH (n) RETURN count(n) as node_count")
if err != nil {
return ""
}
var stats strings.Builder
// 解析结果
if results, ok := result["results"].([]interface{}); ok && len(results) > 0 {
if firstResult, ok := results[0].(map[string]interface{}); ok {
if data, ok := firstResult["data"].([]interface{}); ok && len(data) > 0 {
if row, ok := data[0].(map[string]interface{}); ok {
if rowData, ok := row["row"].([]interface{}); ok && len(rowData) > 0 {
stats.WriteString(fmt.Sprintf("节点总数: %v\n", rowData[0]))
}
}
}
}
}
// 获取关系统计
relResult, err := p.executeQuery(ctx, info, creds, "MATCH ()-[r]->() RETURN count(r) as rel_count")
if err == nil {
if results, ok := relResult["results"].([]interface{}); ok && len(results) > 0 {
if firstResult, ok := results[0].(map[string]interface{}); ok {
if data, ok := firstResult["data"].([]interface{}); ok && len(data) > 0 {
if row, ok := data[0].(map[string]interface{}); ok {
if rowData, ok := row["row"].([]interface{}); ok && len(rowData) > 0 {
stats.WriteString(fmt.Sprintf("关系总数: %v\n", rowData[0]))
}
}
}
}
}
}
return stats.String()
}
// getNodeLabels 获取节点标签列表
func (p *Neo4jPlugin) getNodeLabels(ctx context.Context, info *common.HostInfo, creds Credential) []string {
result, err := p.executeQuery(ctx, info, creds, "CALL db.labels()")
if err != nil {
return nil
}
var labels []string
// 解析结果
if results, ok := result["results"].([]interface{}); ok && len(results) > 0 {
if firstResult, ok := results[0].(map[string]interface{}); ok {
if data, ok := firstResult["data"].([]interface{}); ok {
for _, item := range data {
if row, ok := item.(map[string]interface{}); ok {
if rowData, ok := row["row"].([]interface{}); ok && len(rowData) > 0 {
if label, ok := rowData[0].(string); ok {
labels = append(labels, label)
}
}
}
}
}
}
}
return labels
}
// getRelationshipTypes 获取关系类型列表
func (p *Neo4jPlugin) getRelationshipTypes(ctx context.Context, info *common.HostInfo, creds Credential) []string {
result, err := p.executeQuery(ctx, info, creds, "CALL db.relationshipTypes()")
if err != nil {
return nil
}
var relationships []string
// 解析结果
if results, ok := result["results"].([]interface{}); ok && len(results) > 0 {
if firstResult, ok := results[0].(map[string]interface{}); ok {
if data, ok := firstResult["data"].([]interface{}); ok {
for _, item := range data {
if row, ok := item.(map[string]interface{}); ok {
if rowData, ok := row["row"].([]interface{}); ok && len(rowData) > 0 {
if rel, ok := rowData[0].(string); ok {
relationships = append(relationships, rel)
}
}
}
}
}
}
}
return relationships
}
// getProcedures 获取存储过程列表
func (p *Neo4jPlugin) getProcedures(ctx context.Context, info *common.HostInfo, creds Credential) []string {
result, err := p.executeQuery(ctx, info, creds, "CALL dbms.procedures() YIELD name RETURN name LIMIT 10")
if err != nil {
return nil
}
var procedures []string
// 解析结果
if results, ok := result["results"].([]interface{}); ok && len(results) > 0 {
if firstResult, ok := results[0].(map[string]interface{}); ok {
if data, ok := firstResult["data"].([]interface{}); ok {
for _, item := range data {
if row, ok := item.(map[string]interface{}); ok {
if rowData, ok := row["row"].([]interface{}); ok && len(rowData) > 0 {
if proc, ok := rowData[0].(string); ok {
procedures = append(procedures, proc)
}
}
}
}
}
}
}
return procedures
}
// identifyService 服务识别 - 检测Neo4j服务
func (p *Neo4jPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
baseURL := fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
client := &http.Client{
Timeout: time.Duration(common.Timeout) * time.Second,
}
req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
if err != nil {
return &ScanResult{
Success: false,
Service: "neo4j",
Error: err,
}
}
resp, err := client.Do(req)
if err != nil {
return &ScanResult{
Success: false,
Service: "neo4j",
Error: err,
}
}
defer resp.Body.Close()
// 检查响应头或内容是否包含Neo4j特征
var banner string
if resp.Header.Get("Server") != "" && strings.Contains(strings.ToLower(resp.Header.Get("Server")), "neo4j") {
banner = "Neo4j图数据库 (HTTP接口)"
} else if resp.StatusCode == 200 || resp.StatusCode == 401 {
// 尝试检查根路径响应
body, _ := io.ReadAll(resp.Body)
if strings.Contains(strings.ToLower(string(body)), "neo4j") {
banner = "Neo4j图数据库服务"
} else {
banner = "Neo4j服务 (协议识别)"
}
} else {
return &ScanResult{
Success: false,
Service: "neo4j",
Error: fmt.Errorf("无法识别为Neo4j服务"),
}
}
common.LogSuccess(i18n.GetText("neo4j_service_identified", target, banner))
return &ScanResult{
Success: true,
Service: "neo4j",
Banner: banner,
}
}
// init 自动注册插件
func init() {
RegisterPlugin("neo4j", func() Plugin {
return NewNeo4jPlugin()
})
}