mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-24 04:01:52 +08:00
fix(portfinger): 修复SMB2服务指纹识别和NetInfo输出问题
- 添加SMB2ProgNeg探针支持现代Windows的SMB2协议 - 修复Go regexp对高位字节的UTF-8兼容问题,使用Latin-1转换 - 修复探针失败后连接重建逻辑 - 修复vendor_product字段名不匹配问题 - 修复NetInfo多行输出被其他日志打断的问题
This commit is contained in:
@@ -2,6 +2,7 @@ package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -116,10 +117,22 @@ func (l *Logger) log(level LogLevel, content string) {
|
||||
|
||||
// 格式化消息:保留前缀,去掉时间戳
|
||||
prefix := l.getLevelPrefix(level)
|
||||
logMsg := fmt.Sprintf("%s %s", prefix, content)
|
||||
|
||||
// 输出消息
|
||||
l.outputMessage(level, logMsg)
|
||||
// 处理多行内容:给每行加上前缀,然后作为一个整体输出
|
||||
if strings.Contains(content, "\n") {
|
||||
lines := strings.Split(content, "\n")
|
||||
var formattedLines []string
|
||||
for _, line := range lines {
|
||||
if line != "" {
|
||||
formattedLines = append(formattedLines, fmt.Sprintf("%s %s", prefix, line))
|
||||
}
|
||||
}
|
||||
logMsg := strings.Join(formattedLines, "\n")
|
||||
l.outputMessage(level, logMsg)
|
||||
} else {
|
||||
logMsg := fmt.Sprintf("%s %s", prefix, content)
|
||||
l.outputMessage(level, logMsg)
|
||||
}
|
||||
|
||||
// 根据慢速输出设置决定是否添加延迟
|
||||
if l.config.SlowOutput {
|
||||
|
||||
+1
-1
@@ -322,7 +322,7 @@ func buildServiceLogMessage(addr string, serviceInfo *ServiceInfo, isWeb bool) s
|
||||
|
||||
// 构建 [Product:xxx ||Version:xxx] 格式
|
||||
var info []string
|
||||
if product, ok := serviceInfo.Extras["product"]; ok && product != "" {
|
||||
if product, ok := serviceInfo.Extras["vendor_product"]; ok && product != "" {
|
||||
info = append(info, fmt.Sprintf("Product:%s", product))
|
||||
}
|
||||
if serviceInfo.Version != "" {
|
||||
|
||||
@@ -6,6 +6,32 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BytesToRegexSafeString 将字节切片转换为 Go regexp 安全的正则表达式模式字符串
|
||||
// 非打印字符和高位字节转换为 \x{NN} 形式,用于编译正则表达式
|
||||
func BytesToRegexSafeString(b []byte) string {
|
||||
var result strings.Builder
|
||||
for _, c := range b {
|
||||
if c < 32 || c >= 128 {
|
||||
// 控制字符和高位字节转换为 \x{NN} 格式
|
||||
result.WriteString(fmt.Sprintf("\\x{%02x}", c))
|
||||
} else {
|
||||
result.WriteByte(c)
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// bytesToLatin1String 将字节切片转换为 Latin-1 字符串
|
||||
// 每个字节直接映射到对应的 Unicode 码点 U+0000-U+00FF
|
||||
// 这样可以与使用 \x{NN} 格式的正则表达式正确匹配
|
||||
func bytesToLatin1String(b []byte) string {
|
||||
runes := make([]rune, len(b))
|
||||
for i, c := range b {
|
||||
runes[i] = rune(c)
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
// parseMatchDirective 解析match/softmatch指令的通用实现
|
||||
func (p *Probe) parseMatchDirective(data, prefix string, isSoft bool) (Match, error) {
|
||||
match := Match{IsSoft: isSoft}
|
||||
@@ -23,13 +49,22 @@ func (p *Probe) parseMatchDirective(data, prefix string, isSoft bool) (Match, er
|
||||
pattern := textSplited[0]
|
||||
versionInfo := strings.Join(textSplited[1:], "")
|
||||
|
||||
// versionInfo 格式是 "flags p/product/ v/version/ ..."
|
||||
// flags 是正则表达式修饰符(如 s、i、si),后面跟空格和版本信息字段
|
||||
// 需要跳过 flags 部分,找到第一个空格开始的版本信息
|
||||
if idx := strings.Index(versionInfo, " "); idx != -1 {
|
||||
versionInfo = versionInfo[idx:]
|
||||
}
|
||||
|
||||
// 解码并编译正则表达式
|
||||
patternUnescaped, decodeErr := DecodePattern(pattern)
|
||||
if decodeErr != nil {
|
||||
return match, decodeErr
|
||||
}
|
||||
|
||||
patternCompiled, compileErr := regexp.Compile(string(patternUnescaped))
|
||||
// 将字节模式转换为 Go regexp 安全的字符串(处理高位字节)
|
||||
safePattern := BytesToRegexSafeString(patternUnescaped)
|
||||
patternCompiled, compileErr := regexp.Compile(safePattern)
|
||||
if compileErr != nil {
|
||||
return match, compileErr
|
||||
}
|
||||
@@ -58,10 +93,13 @@ func (m *Match) MatchPattern(response []byte) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
matched := m.PatternCompiled.Match(response)
|
||||
// 将响应字节转换为 Latin-1 字符串,每个字节映射到对应的 Unicode 码点
|
||||
// 这样正则表达式中的 \x{NN} 可以正确匹配对应的字节值
|
||||
latin1Response := bytesToLatin1String(response)
|
||||
matched := m.PatternCompiled.MatchString(latin1Response)
|
||||
if matched {
|
||||
// 提取匹配到的子组
|
||||
submatches := m.PatternCompiled.FindStringSubmatch(string(response))
|
||||
submatches := m.PatternCompiled.FindStringSubmatch(latin1Response)
|
||||
if len(submatches) > 1 {
|
||||
m.FoundItems = submatches[1:] // 排除完整匹配,只保留分组
|
||||
}
|
||||
|
||||
@@ -13987,7 +13987,14 @@ match sap-gui m|^\0\0\0\x0e\*\*DPTMMSG\*\*\0\0\xf8| p/SAP Gui Dispatcher/ cpe:/a
|
||||
softmatch smpp m|^\0\0\0\x10\x80\0\0\0\0\0\0\x03....$|s
|
||||
softmatch postgresql m|^E\0\0\0.SFATAL\0(?:VFATAL\0)?C\w{5}\0M| p/PostgreSQL DB/ cpe:/a:postgresql:postgresql/a
|
||||
|
||||
# SMB Negotiate Protocol
|
||||
# SMB Multi-Protocol Negotiate (SMB1 format with SMB2 dialects)
|
||||
##############################NEXT PROBE##############################
|
||||
Probe TCP SMB2ProgNeg q|\0\0\0\x45\xffSMBr\0\0\0\0\x18\x01\x48\0\0\0\0\0\0\0\0\0\0\0\0\xff\xff\xac\x03\0\0\0\0\0\x22\0\x02NT LM 0.12\0\x02SMB 2.002\0\x02SMB 2.???\0|
|
||||
rarity 3
|
||||
ports 139,445
|
||||
match microsoft-ds m|^\0\0..\xfeSMB\x40\0|s p/Microsoft Windows SMB2/ o/Windows/ cpe:/o:microsoft:windows/a
|
||||
|
||||
# SMB Negotiate Protocol (SMB1 - for legacy systems)
|
||||
##############################NEXT PROBE##############################
|
||||
Probe TCP SMBProgNeg q|\0\0\0\xa4\xff\x53\x4d\x42\x72\0\0\0\0\x08\x01\x40\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x40\x06\0\0\x01\0\0\x81\0\x02PC NETWORK PROGRAM 1.0\0\x02MICROSOFT NETWORKS 1.03\0\x02MICROSOFT NETWORKS 3.0\0\x02LANMAN1.0\0\x02LM1.2X002\0\x02Samba\0\x02NT LANMAN 1.0\0\x02NT LM 0.12\0|
|
||||
rarity 4
|
||||
|
||||
@@ -29,12 +29,7 @@ var (
|
||||
func (m *Match) ParseVersionInfo(response []byte) Extras {
|
||||
var extras = Extras{}
|
||||
|
||||
// 确保有匹配项
|
||||
if len(m.FoundItems) == 0 {
|
||||
return extras
|
||||
}
|
||||
|
||||
// 替换版本信息中的占位符(单次扫描)
|
||||
// 替换版本信息中的占位符(如 $1, $2 等)
|
||||
versionInfo := m.VersionInfo
|
||||
if len(m.FoundItems) > 0 {
|
||||
replacements := make([]string, 0, len(m.FoundItems)*2)
|
||||
|
||||
@@ -225,6 +225,8 @@ func (s *SmartPortInfoScanner) tryProbeList(probes []*Probe, usedProbes map[stri
|
||||
|
||||
response := s.info.Connect(probeData)
|
||||
if len(response) == 0 {
|
||||
// 连接可能被关闭(如服务端返回 EOF),尝试重建连接后继续下一个探针
|
||||
s.reconnectIfNeeded()
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -238,6 +240,26 @@ func (s *SmartPortInfoScanner) tryProbeList(probes []*Probe, usedProbes map[stri
|
||||
return false
|
||||
}
|
||||
|
||||
// reconnectIfNeeded 强制重建连接
|
||||
// 当探针收到空响应时调用,说明连接可能已被服务端关闭
|
||||
func (s *SmartPortInfoScanner) reconnectIfNeeded() {
|
||||
// 关闭旧连接
|
||||
if s.info.Conn != nil {
|
||||
_ = s.info.Conn.Close()
|
||||
s.info.Conn = nil
|
||||
s.Conn = nil
|
||||
}
|
||||
|
||||
// 重新建立连接
|
||||
newConn, err := common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%d", s.Address, s.Port), s.Timeout)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.info.Conn = newConn
|
||||
s.Conn = newConn
|
||||
}
|
||||
|
||||
// performSSLSecondStage 执行 SSL 多阶段探测
|
||||
// 参考 gonmap 的策略:ssl → ssl-specific probes → https
|
||||
func (s *SmartPortInfoScanner) performSSLSecondStage(serviceInfo *ServiceInfo) *ServiceInfo {
|
||||
|
||||
@@ -76,15 +76,20 @@ func (p *FindNetPlugin) Scan(ctx context.Context, info *common.HostInfo, config
|
||||
|
||||
state.IncrementTCPSuccessPacketCount()
|
||||
|
||||
// 记录发现的网络信息 (每行一个IP,便于阅读)
|
||||
// 记录发现的网络信息 (一次性输出,避免被其他日志打断)
|
||||
if networkInfo.Valid {
|
||||
// 输出主机名
|
||||
var lines []string
|
||||
// 主机名行
|
||||
if networkInfo.Hostname != "" {
|
||||
common.LogSuccess(fmt.Sprintf("NetInfo %s [%s]", target, networkInfo.Hostname))
|
||||
lines = append(lines, fmt.Sprintf("NetInfo %s [%s]", target, networkInfo.Hostname))
|
||||
}
|
||||
// 每个IP单独一行
|
||||
for _, ip := range networkInfo.IPv4Addrs {
|
||||
common.LogSuccess(fmt.Sprintf("NetInfo %s -> %s", target, ip))
|
||||
lines = append(lines, fmt.Sprintf("NetInfo %s -> %s", target, ip))
|
||||
}
|
||||
// 一次性输出所有行
|
||||
if len(lines) > 0 {
|
||||
common.LogSuccess(strings.Join(lines, "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user