fscan/plugins/telnet.go
ZacharyZcR 678d750c8a refactor: 重构插件架构,实现单文件插件系统
将复杂的三文件插件架构(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

验证通过: 新系统编译运行正常,所有插件功能验证通过
2025-08-25 23:57:00 +08:00

452 lines
11 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 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()
})
}