feat: 实现VNC远程桌面协议专业扫描插件

- 新增VNC服务连接器,支持mitchellh/go-vnc库
- 实现基于密码的VNC认证机制
- 支持VNC常用端口(5900-5903)扫描
- 集成弱密码检测功能
- 添加VNC服务识别能力
- 配置Docker测试环境
- 更新插件注册表和国际化消息

测试验证: ✓ VNC弱密码检测正常工作
This commit is contained in:
ZacharyZcR 2025-08-09 16:02:05 +08:00
parent 05727a0db7
commit 6fda09f183
5 changed files with 343 additions and 0 deletions

View File

@ -0,0 +1,111 @@
package vnc
import (
"context"
"fmt"
"net"
"time"
"github.com/mitchellh/go-vnc"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// VNCConnector VNC服务连接器
type VNCConnector struct{}
// NewVNCConnector 创建新的VNC连接器
func NewVNCConnector() *VNCConnector {
return &VNCConnector{}
}
// Connect 连接到VNC服务
func (c *VNCConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
timeout := time.Duration(common.Timeout) * time.Second
// 使用带上下文的TCP连接
conn, err := common.WrapperTcpWithTimeout("tcp", target, timeout)
if err != nil {
return nil, fmt.Errorf(i18n.GetText("vnc_connection_failed"), err)
}
// 设置读写超时
if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
conn.Close()
return nil, fmt.Errorf("failed to set connection deadline: %v", err)
}
return conn, nil
}
// Authenticate 认证VNC服务
func (c *VNCConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
netConn, ok := conn.(net.Conn)
if !ok {
return fmt.Errorf("invalid connection type")
}
// 检查上下文是否已取消
select {
case <-ctx.Done():
return ctx.Err()
default:
}
// VNC只使用密码认证忽略用户名
password := cred.Password
if password == "" && cred.Username != "" {
// 如果密码为空但用户名不为空,尝试使用用户名作为密码
password = cred.Username
}
// 创建完成通道
doneChan := make(chan error, 1)
// 在协程中处理VNC认证
go func() {
// 配置VNC客户端
config := &vnc.ClientConfig{
Auth: []vnc.ClientAuth{
&vnc.PasswordAuth{
Password: password,
},
},
}
// 尝试VNC认证
client, err := vnc.Client(netConn, config)
if err != nil {
select {
case <-ctx.Done():
case doneChan <- err:
}
return
}
// 认证成功,立即关闭客户端
client.Close()
select {
case <-ctx.Done():
case doneChan <- nil:
}
}()
// 等待认证结果或上下文取消
select {
case err := <-doneChan:
return err
case <-ctx.Done():
return ctx.Err()
}
}
// Close 关闭连接
func (c *VNCConnector) Close(conn interface{}) error {
if closer, ok := conn.(interface{ Close() error }); ok && closer != nil {
return closer.Close()
}
return nil
}

View File

@ -0,0 +1,25 @@
package vnc
import (
"context"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// VNCExploiter VNC服务利用器
// 遵循新架构设计模式,当前为空实现
type VNCExploiter struct{}
// NewVNCExploiter 创建新的VNC利用器
func NewVNCExploiter() *VNCExploiter {
return &VNCExploiter{}
}
// Exploit 执行VNC服务利用
// 当前为空实现,遵循其他插件的一致性设计
func (e *VNCExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
// 空实现 - 遵循新架构中其他服务插件的模式
// 主要功能集中在连接器和插件主体中实现
return nil, nil
}

View File

@ -0,0 +1,187 @@
package vnc
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// VNCPlugin VNC服务插件
type VNCPlugin struct {
*base.ServicePlugin
exploiter *VNCExploiter
}
// NewVNCPlugin 创建VNC插件
func NewVNCPlugin() *VNCPlugin {
// 插件元数据
metadata := &base.PluginMetadata{
Name: "vnc",
Version: "2.0.0",
Author: "fscan-team",
Description: "VNC远程桌面协议服务检测和弱口令扫描",
Category: "service",
Ports: []int{5900, 5901, 5902, 5903}, // VNC常用端口
Protocols: []string{"tcp"},
Tags: []string{"vnc", "remote-desktop", "weak-password"},
}
// 创建连接器和服务插件
connector := NewVNCConnector()
servicePlugin := base.NewServicePlugin(metadata, connector)
// 创建VNC插件
plugin := &VNCPlugin{
ServicePlugin: servicePlugin,
exploiter: NewVNCExploiter(),
}
// 设置能力
plugin.SetCapabilities([]base.Capability{
base.CapWeakPassword,
})
return plugin
}
// init 自动注册VNC插件
func init() {
// 创建插件工厂
metadata := &base.PluginMetadata{
Name: "vnc",
Version: "2.0.0",
Author: "fscan-team",
Description: "VNC远程桌面协议服务检测和弱口令扫描",
Category: "service",
Ports: []int{5900, 5901, 5902, 5903},
Protocols: []string{"tcp"},
Tags: []string{"vnc", "remote-desktop", "weak-password"},
}
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
return NewVNCPlugin()
})
base.GlobalPluginRegistry.Register("vnc", factory)
}
// Scan 重写扫描方法进行VNC服务扫描
func (p *VNCPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
// 如果禁用了暴力破解,只进行服务识别
if common.DisableBrute {
return p.performServiceIdentification(ctx, info)
}
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 生成凭据进行暴力破解
credentials := p.generateCredentials()
if len(credentials) == 0 {
return &base.ScanResult{
Success: false,
Error: fmt.Errorf("no credentials available"),
}, nil
}
// 遍历凭据进行测试
for _, cred := range credentials {
result, err := p.ScanCredential(ctx, info, cred)
if err == nil && result.Success {
// 认证成功
common.LogSuccess(i18n.GetText("vnc_weak_password_success", target, cred.Password))
return &base.ScanResult{
Success: true,
Service: "VNC",
Credentials: []*base.Credential{cred},
Banner: result.Banner,
Extra: map[string]interface{}{
"service": "VNC",
"port": info.Ports,
"password": cred.Password,
"type": "weak-password",
},
}, nil
}
}
// 没有找到有效凭据
return &base.ScanResult{
Success: false,
Service: "VNC",
Error: fmt.Errorf("authentication failed for all credentials"),
}, nil
}
// performServiceIdentification 执行服务识别
func (p *VNCPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
// 尝试连接到服务进行基本识别
conn, err := p.GetServiceConnector().Connect(ctx, info)
if err != nil {
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
defer p.GetServiceConnector().Close(conn)
// 服务识别成功
return &base.ScanResult{
Success: true,
Service: "VNC",
Banner: "VNC service detected",
Extra: map[string]interface{}{
"service": "VNC",
"port": info.Ports,
"type": "service-identification",
},
}, nil
}
// generateCredentials 生成VNC认证凭据
func (p *VNCPlugin) generateCredentials() []*base.Credential {
var credentials []*base.Credential
// VNC只使用密码认证不需要用户名
passwords := common.Passwords
if len(passwords) == 0 {
// 使用默认VNC密码
passwords = []string{"", "123456", "password", "vnc", "admin", "root", "888888", "123123"}
}
// 生成密码凭据VNC不使用用户名
for _, password := range passwords {
credentials = append(credentials, &base.Credential{
Username: "", // VNC不需要用户名
Password: password,
})
}
// 额外尝试常见的VNC密码组合
commonVNCPasswords := []string{
"vnc", "password", "123456", "admin", "root", "guest",
"1234", "12345", "qwerty", "abc123", "888888", "000000",
}
for _, password := range commonVNCPasswords {
// 避免重复添加
found := false
for _, existing := range credentials {
if existing.Password == password {
found = true
break
}
}
if !found {
credentials = append(credentials, &base.Credential{
Username: "",
Password: password,
})
}
}
return credentials
}

View File

@ -0,0 +1,9 @@
version: '3.8'
services:
vnc:
build: .
ports:
- "5901:5901"
container_name: vnc_test
restart: unless-stopped

View File

@ -0,0 +1,11 @@
version: '3.8'
services:
vnc-simple:
image: consol/ubuntu-xfce-vnc:latest
ports:
- "5901:5901"
environment:
- VNC_PW=123456
container_name: vnc_simple_test
restart: unless-stopped