fscan/plugins/services/telnet.go
ZacharyZcR e082e2bb59 refactor: 重组插件目录结构,提升管理直观性
将所有服务插件移动到plugins/services/目录下,使目录结构更加清晰直观:
• 创建plugins/services/目录统一管理服务扫描插件
• 添加init.go提供类型别名和函数导出
• 更新main.go导入路径
• 所有20个服务插件功能验证正常

新的目录结构更便于插件管理和维护。
2025-08-26 00:02:13 +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 services
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()
})
}