mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00
fix: 完全重写Telnet插件,修复IAC协商和认证检测问题
## 主要修复 - 重写Telnet插件认证逻辑,修复"未发现弱密码"错误 - 实现完整IAC协商处理,确保与telnet服务器正常通信 - 改进登录提示和认证流程检测,支持多轮数据读取 - 优化shell提示符检测,准确识别无需认证的服务 - 添加详细调试日志,方便问题排查 ## 技术改进 - 实现handleIACNegotiation函数处理telnet协议协商 - 改进cleanResponse函数清理IAC控制命令 - 增强performSimpleTelnetAuth多阶段认证检测 - 分离isLoginSuccess和isLoginFailed判断逻辑 - 优化超时处理和错误恢复机制 ## 测试结果 - 正确识别无需认证的busybox telnetd服务 - 能够准确检测和报告"无需认证"状态 - 修复随机密码"成功"的虚假结果问题 - IAC协商成功,获得真实服务器响应
This commit is contained in:
parent
3ab0405df2
commit
7579549e94
3
go.mod
3
go.mod
@ -15,6 +15,8 @@ require (
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/schollz/progressbar/v3 v3.13.1
|
||||
github.com/stacktitan/smb v0.0.0-20190531122847-da9a425dceb8
|
||||
github.com/ziutek/telnet v0.1.0
|
||||
go.mongodb.org/mongo-driver v1.17.4
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/net v0.32.0
|
||||
golang.org/x/sync v0.10.0
|
||||
@ -63,7 +65,6 @@ require (
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.4 // indirect
|
||||
golang.org/x/term v0.27.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
|
6
go.sum
6
go.sum
@ -30,7 +30,6 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-ldap/ldap/v3 v3.4.9 h1:KxX9eO44/MpqPXVVMPJDB+k/35GEePHE/Jfvl7oRMUo=
|
||||
@ -86,7 +85,6 @@ github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
@ -136,7 +134,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
@ -146,6 +143,8 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/ziutek/telnet v0.1.0 h1:Fds2AqweYyoRHX/5X8ikiyqIcSl156Sf2xCvURfqXHA=
|
||||
github.com/ziutek/telnet v0.1.0/go.mod h1:3M/h4qudUBZA8n+N4ywQIu2auiHUJNdqLUIKDAbG2M4=
|
||||
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
|
||||
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@ -241,7 +240,6 @@ google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
@ -21,34 +21,31 @@ func NewTelnetPlugin() *TelnetPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (p *TelnetPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
|
||||
func (p *TelnetPlugin) Scan(ctx context.Context, info *common.HostInfo) *plugins.Result {
|
||||
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(fmt.Sprintf("Telnet %s 未授权访问", target))
|
||||
return result
|
||||
}
|
||||
|
||||
credentials := GenerateCredentials("telnet")
|
||||
if len(credentials) == 0 {
|
||||
return &ScanResult{
|
||||
Success: false,
|
||||
Service: "telnet",
|
||||
Error: fmt.Errorf("没有可用的测试凭据"),
|
||||
}
|
||||
}
|
||||
// 构建凭据列表
|
||||
credentials := plugins.GenerateCredentials("telnet")
|
||||
|
||||
for _, cred := range credentials {
|
||||
if p.testCredential(ctx, info, cred) {
|
||||
common.LogSuccess(fmt.Sprintf("Telnet %s %s:%s", target, cred.Username, cred.Password))
|
||||
// 检查上下文是否已取消
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Service: "telnet",
|
||||
Error: ctx.Err(),
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
return &ScanResult{
|
||||
if p.testTelnetCredential(ctx, info, cred) {
|
||||
common.LogSuccess(fmt.Sprintf("Telnet %s %s:%s", target, cred.Username, cred.Password))
|
||||
return &plugins.Result{
|
||||
Success: true,
|
||||
Service: "telnet",
|
||||
Username: cred.Username,
|
||||
@ -57,135 +54,259 @@ func (p *TelnetPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanRes
|
||||
}
|
||||
}
|
||||
|
||||
return &ScanResult{
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Service: "telnet",
|
||||
Error: fmt.Errorf("未发现弱密码"),
|
||||
}
|
||||
}
|
||||
|
||||
// testTelnetCredential 测试telnet凭据
|
||||
func (p *TelnetPlugin) testTelnetCredential(ctx context.Context, info *common.HostInfo, cred plugins.Credential) bool {
|
||||
address := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||||
|
||||
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])
|
||||
|
||||
if strings.Contains(welcome, "$") || strings.Contains(welcome, "#") || strings.Contains(welcome, ">") {
|
||||
return &ScanResult{
|
||||
Success: true,
|
||||
Service: "telnet",
|
||||
Banner: "未授权访问",
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
// 创建带超时的连接
|
||||
conn, err := net.DialTimeout("tcp", address, 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))
|
||||
// 设置超时
|
||||
deadline := time.Now().Add(time.Duration(common.Timeout) * time.Second)
|
||||
conn.SetDeadline(deadline)
|
||||
|
||||
// 简单的telnet认证流程
|
||||
return p.performSimpleTelnetAuth(conn, cred.Username, cred.Password)
|
||||
}
|
||||
|
||||
// performSimpleTelnetAuth 执行简单的telnet认证
|
||||
func (p *TelnetPlugin) performSimpleTelnetAuth(conn net.Conn, username, password string) bool {
|
||||
buffer := make([]byte, 1024)
|
||||
|
||||
// 处理IAC协商并等待真正的登录提示
|
||||
loginPromptReceived := false
|
||||
attempts := 0
|
||||
maxAttempts := 10 // 最多尝试10次读取
|
||||
|
||||
for attempts < maxAttempts && !loginPromptReceived {
|
||||
attempts++
|
||||
|
||||
// 设置较短的读取超时
|
||||
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
common.LogDebug(fmt.Sprintf("第%d次读取失败: %v", attempts, err))
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
response := string(buffer[:n])
|
||||
|
||||
// 处理IAC协商
|
||||
p.handleIACNegotiation(conn, buffer[:n])
|
||||
|
||||
// 清理响应
|
||||
cleaned := p.cleanResponse(response)
|
||||
common.LogDebug(fmt.Sprintf("第%d次响应[%s:%s]: %q -> %q", attempts, username, password, response, cleaned))
|
||||
|
||||
// 检查是否为shell提示符(无需认证)
|
||||
if p.isShellPrompt(cleaned) {
|
||||
common.LogDebug(fmt.Sprintf("检测到shell提示符,无需认证"))
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查是否收到登录提示
|
||||
if strings.Contains(strings.ToLower(cleaned), "login") ||
|
||||
strings.Contains(strings.ToLower(cleaned), "username") ||
|
||||
strings.Contains(cleaned, ":") { // 简单的提示符检测
|
||||
loginPromptReceived = true
|
||||
common.LogDebug(fmt.Sprintf("检测到登录提示"))
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
|
||||
if !loginPromptReceived {
|
||||
common.LogDebug(fmt.Sprintf("未在%d次尝试中检测到登录提示", maxAttempts))
|
||||
return false
|
||||
}
|
||||
|
||||
// 发送用户名
|
||||
common.LogDebug(fmt.Sprintf("发送用户名: %s", username))
|
||||
_, err := conn.Write([]byte(username + "\r\n"))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
data := string(buffer[:n])
|
||||
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)
|
||||
passwordPromptReceived := false
|
||||
attempts = 0
|
||||
|
||||
n, err = conn.Read(buffer)
|
||||
for attempts < 5 && !passwordPromptReceived {
|
||||
attempts++
|
||||
|
||||
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
common.LogDebug(fmt.Sprintf("读取密码提示第%d次失败: %v", attempts, err))
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
response := string(buffer[:n])
|
||||
cleaned := p.cleanResponse(response)
|
||||
common.LogDebug(fmt.Sprintf("密码提示第%d次响应: %q -> %q", attempts, response, cleaned))
|
||||
|
||||
if strings.Contains(strings.ToLower(cleaned), "password") ||
|
||||
strings.Contains(cleaned, ":") {
|
||||
passwordPromptReceived = true
|
||||
common.LogDebug(fmt.Sprintf("检测到密码提示"))
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
|
||||
if !passwordPromptReceived {
|
||||
common.LogDebug(fmt.Sprintf("未检测到密码提示"))
|
||||
return false
|
||||
}
|
||||
|
||||
// 发送密码
|
||||
common.LogDebug(fmt.Sprintf("发送密码: %s", password))
|
||||
_, err = conn.Write([]byte(password + "\r\n"))
|
||||
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)
|
||||
// 检查登录结果
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
attempts = 0
|
||||
|
||||
n, err = conn.Read(buffer)
|
||||
for attempts < 5 {
|
||||
attempts++
|
||||
|
||||
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
common.LogDebug(fmt.Sprintf("读取登录结果第%d次失败: %v", attempts, err))
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
response := string(buffer[:n])
|
||||
cleaned := p.cleanResponse(response)
|
||||
common.LogDebug(fmt.Sprintf("登录结果第%d次响应: %q -> %q", attempts, response, cleaned))
|
||||
|
||||
// 检查登录成功或失败
|
||||
if p.isLoginSuccess(cleaned) {
|
||||
common.LogDebug(fmt.Sprintf("登录成功!"))
|
||||
return true
|
||||
}
|
||||
|
||||
if p.isLoginFailed(cleaned) {
|
||||
common.LogDebug(fmt.Sprintf("登录失败!"))
|
||||
return false
|
||||
}
|
||||
|
||||
result := string(buffer[:n])
|
||||
result = p.cleanTelnetData(result)
|
||||
|
||||
return p.isLoginSuccessful(result)
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
|
||||
common.LogDebug(fmt.Sprintf("无法确定登录结果"))
|
||||
return false
|
||||
}
|
||||
|
||||
// handleIACNegotiation 处理IAC协商
|
||||
func (p *TelnetPlugin) handleIACNegotiation(conn net.Conn, data []byte) {
|
||||
for i := 0; i < len(data); i++ {
|
||||
if data[i] == 255 && i+2 < len(data) { // IAC
|
||||
cmd := data[i+1]
|
||||
opt := data[i+2]
|
||||
|
||||
func (p *TelnetPlugin) cleanTelnetData(data string) string {
|
||||
cleaned := ""
|
||||
// 简单响应策略:拒绝所有选项
|
||||
switch cmd {
|
||||
case 251: // WILL
|
||||
// 回应DONT
|
||||
conn.Write([]byte{255, 254, opt})
|
||||
common.LogDebug(fmt.Sprintf("IAC响应: DONT %d", opt))
|
||||
case 253: // DO
|
||||
// 回应WONT
|
||||
conn.Write([]byte{255, 252, opt})
|
||||
common.LogDebug(fmt.Sprintf("IAC响应: WONT %d", opt))
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cleanResponse 清理telnet响应中的IAC命令
|
||||
func (p *TelnetPlugin) cleanResponse(data string) string {
|
||||
var result strings.Builder
|
||||
for i := 0; i < len(data); i++ {
|
||||
b := data[i]
|
||||
// 跳过IAC命令序列
|
||||
if b == 255 && i+2 < len(data) {
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
if b >= 32 && b <= 126 || b == '\r' || b == '\n' {
|
||||
cleaned += string(b)
|
||||
// 保留可打印字符
|
||||
if (b >= 32 && b <= 126) || b == '\r' || b == '\n' {
|
||||
result.WriteByte(b)
|
||||
}
|
||||
}
|
||||
return cleaned
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func (p *TelnetPlugin) isLoginSuccessful(data string) bool {
|
||||
// isShellPrompt 检查是否为shell提示符
|
||||
func (p *TelnetPlugin) isShellPrompt(data string) bool {
|
||||
data = strings.ToLower(data)
|
||||
return strings.Contains(data, "$") ||
|
||||
strings.Contains(data, "#") ||
|
||||
strings.Contains(data, ">")
|
||||
}
|
||||
|
||||
// isLoginSuccess 检查登录是否成功
|
||||
func (p *TelnetPlugin) isLoginSuccess(data string) bool {
|
||||
data = strings.ToLower(data)
|
||||
|
||||
successIndicators := []string{"$", "#", ">", "welcome", "last login"}
|
||||
for _, indicator := range successIndicators {
|
||||
if strings.Contains(data, indicator) {
|
||||
// 检查成功标识
|
||||
if strings.Contains(data, "$") ||
|
||||
strings.Contains(data, "#") ||
|
||||
strings.Contains(data, ">") ||
|
||||
strings.Contains(data, "welcome") ||
|
||||
strings.Contains(data, "last login") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
failIndicators := []string{"incorrect", "failed", "denied", "invalid", "login:"}
|
||||
for _, indicator := range failIndicators {
|
||||
if strings.Contains(data, indicator) {
|
||||
return false
|
||||
}
|
||||
|
||||
// isLoginFailed 检查登录是否失败
|
||||
func (p *TelnetPlugin) isLoginFailed(data string) bool {
|
||||
data = strings.ToLower(data)
|
||||
|
||||
// 检查失败标识
|
||||
if strings.Contains(data, "incorrect") ||
|
||||
strings.Contains(data, "failed") ||
|
||||
strings.Contains(data, "denied") ||
|
||||
strings.Contains(data, "invalid") ||
|
||||
strings.Contains(data, "login:") || // 重新出现登录提示
|
||||
strings.Contains(data, "username:") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult {
|
||||
func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInfo) *plugins.Result {
|
||||
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{
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Service: "telnet",
|
||||
Error: err,
|
||||
@ -198,7 +319,7 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf
|
||||
buffer := make([]byte, 1024)
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
return &ScanResult{
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Service: "telnet",
|
||||
Error: err,
|
||||
@ -206,7 +327,21 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf
|
||||
}
|
||||
|
||||
data := string(buffer[:n])
|
||||
data = p.cleanTelnetData(data)
|
||||
// 清理telnet数据
|
||||
cleaned := ""
|
||||
for i := 0; i < len(data); i++ {
|
||||
b := data[i]
|
||||
// 跳过telnet控制字符
|
||||
if b == 255 && i+2 < len(data) {
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
// 只保留可打印字符和换行符
|
||||
if (b >= 32 && b <= 126) || b == '\r' || b == '\n' {
|
||||
cleaned += string(b)
|
||||
}
|
||||
}
|
||||
data = cleaned
|
||||
|
||||
var banner string
|
||||
if strings.Contains(strings.ToLower(data), "login") || strings.Contains(strings.ToLower(data), "username") {
|
||||
@ -219,7 +354,7 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf
|
||||
|
||||
common.LogSuccess(fmt.Sprintf("Telnet %s %s", target, banner))
|
||||
|
||||
return &ScanResult{
|
||||
return &plugins.Result{
|
||||
Success: true,
|
||||
Service: "telnet",
|
||||
Banner: banner,
|
||||
@ -227,8 +362,7 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf
|
||||
}
|
||||
|
||||
func init() {
|
||||
// 使用高效注册方式:直接传递端口信息,避免实例创建
|
||||
RegisterPluginWithPorts("telnet", func() Plugin {
|
||||
plugins.RegisterWithPorts("telnet", func() plugins.Plugin {
|
||||
return NewTelnetPlugin()
|
||||
}, []int{23, 2323})
|
||||
}
|
Loading…
Reference in New Issue
Block a user