Skip to content

读读 fingerprintx _一个端口指纹识别工具

字数
1205 字
阅读时间
6 分钟
更新日期
10/16/2022

读读 fingerprintx ,一个端口指纹识别工具

GitHub:https://github.com/praetorian-inc/fingerprintx

fingerprintx是一个类似于httpx的实用程序,它还支持 RDP、SSH、MySQL、PostgreSQL、Kafka 等指纹识别服务。fingerprintx可以与Naabu等端口扫描仪一起使用,对端口扫描期间识别的一组端口进行指纹识别。例如,工程师可能希望扫描 IP 范围,然后快速识别在所有发现的端口上运行的服务。

输入ip+端口,就能输出端口服务指纹相关的信息

支持的协议:

SERVICETRANSPORTSERVICETRANSPORT
HTTPTCPREDISTCP
SSHTCPMQTT3TCP
MODBUSTCPVNCTCP
TELNETTCPMQTT5TCP
FTPTCPRSYNCTCP
SMBTCPRPCTCP
DNSTCPOracleDBTCP
SMTPTCPRTSPTCP
PostgreSQLTCPMQTT5TCP (TLS)
RDPTCPHTTPSTCP (TLS)
POP3TCPSMTPSTCP (TLS)
KAFKATCPMQTT3TCP (TLS)
MySQLTCPRDPTCP (TLS)
MSSQLTCPPOP3STCP (TLS)
LDAPTCPLDAPSTCP (TLS)
IMAPTCPIMAPSTCP (TLS)
SNMPUDPKafkaTCP (TLS)
OPENVPNUDPNETBIOS-NSUDP
IPSECUDPDHCPUDP
STUNUDPNTPUDP
DNSUDP

想看看源码,这些协议是怎么做识别以及怎么组织的。

看官方描述,使用fingerprintx有一个快速模式fast

该fast模式将仅尝试为每个目标识别与该端口关联的默认服务。例如,如果praetorian.com:8443是输入,则只会https运行插件。如果https未在 上运行praetorian.com:8443,则不会有输出。为什么要这样做?这是在大量主机列表中识别大多数服务的快速方法(想想2/8原则 )。

和nmap的区别

一个在 8080 端口打开的服务器上运行的插件是 http 插件。默认服务方法在最好的情况下减少了扫描时间。大多数情况下,在端口 80、443、22 上运行的服务是 http、https 和 ssh——所以这是fingerprintx首先检查的内容。

插件组织结构

这个项目提供了很好的一个插件架构,fingerprintx的指纹识别是以插件的形式进行的,如ftp识别是一个插件,mysql识别也是一个插件。

插件目录位于pkg/plugins/services

虽然不是动态插件加载,作为go的也值得学习。

插件的接口是

go
type Plugin interface {
  Run(net.Conn, PluginConfig) (*PluginResults, error) // 运行插件,返回结果
  PortPriority(uint16) bool // 返回端口的优先级,比如ssh的端口优先级是22,优先级可以让识别更快
  Name() string  // 返回服务插件的名称
  Type() Protocol // 返回该插件的协议类型 TCP或UDP
  Priority() int // 插件调用的优先级,数字越大优先级越高
}

所有的插件都要实现这些方法。

看一个简单的插件源码,例如ftp

go
package ftp

import (
  "net"
  "regexp"

  "github.com/praetorian-inc/fingerprintx/pkg/plugins"
  utils "github.com/praetorian-inc/fingerprintx/pkg/plugins/pluginutils"
)

var ftpResponse = regexp.MustCompile(`^\d{3}[- ](.*)\r`)

const FTP = "ftp"

type FTPPlugin struct{}

func init() {
  plugins.RegisterPlugin(&FTPPlugin{})
}

func (p *FTPPlugin) Run(conn net.Conn, config plugins.PluginConfig) (*plugins.PluginResults, error) {
  response, err := utils.Recv(conn, config.Timeout)
  if err != nil {
    return nil, err
  }
  if len(response) == 0 {
    return nil, nil
  }

  matches := ftpResponse.FindStringSubmatch(string(response))
  if matches == nil {
    return nil, nil
  }

  return &plugins.PluginResults{
    Info: map[string]any{
      "banner": string(response),
    }}, nil
}

func (p *FTPPlugin) PortPriority(i uint16) bool {
  return i == 21
}

func (p *FTPPlugin) Name() string {
  return FTP
}

func (p *FTPPlugin) Type() plugins.Protocol {
  return plugins.TCP
}

func (p *FTPPlugin) Priority() int {
  return 10
}

每个插件初始化时候都会进行默认注册

go
func init() {
  plugins.RegisterPlugin(&FTPPlugin{})
}

跟进RegisterPlugin函数

go

var Plugins = make(map[Protocol][]Plugin)
var pluginIDs = make(map[PluginID]bool)

// This function must not be run concurrently.
// This function should only be run once per plugin.
func RegisterPlugin(p Plugin) {
  id := CreatePluginID(p)
  if pluginIDs[id] {
    panic(fmt.Sprintf("plugin: Register called twice for driver %+v\n", id))
  }

  pluginIDs[id] = true

  var pluginList []Plugin
  if list, exists := Plugins[p.Type()]; exists {
    pluginList = list
  } else {
    pluginList = make([]Plugin, 0)
  }

  Plugins[p.Type()] = append(pluginList, p)
}

他会把实例化的类加入到Plugins这个全局变量中。在程序初始化中,pkg/scan/plugin_list.go 进行初始化所有插件。

后面运行直接遍历Plugins全局变量的内容即可实现插件化调用了。

识别流程

初始化插件,以及对每个类别的插件进行排序,按照协议类型TCPTCPTLSUDP整理

go
func setupPlugins() {
  if len(sortedTCPPlugins) > 0 {
    // already sorted
    return
  }

  sortedTCPPlugins = append(sortedTCPPlugins, plugins.Plugins[plugins.TCP]...)
  sortedTCPTLSPlugins = append(sortedTCPTLSPlugins, plugins.Plugins[plugins.TCPTLS]...)
  sortedUDPPlugins = append(sortedUDPPlugins, plugins.Plugins[plugins.UDP]...)

  sort.Slice(sortedTCPPlugins, func(i, j int) bool {
    return sortedTCPPlugins[i].Priority() < sortedTCPPlugins[j].Priority()
  })
  sort.Slice(sortedUDPPlugins, func(i, j int) bool {
    return sortedUDPPlugins[i].Priority() < sortedUDPPlugins[j].Priority()
  })
  sort.Slice(sortedTCPTLSPlugins, func(i, j int) bool {
    return sortedTCPTLSPlugins[i].Priority() < sortedTCPTLSPlugins[j].Priority()
  })
}

fingerxprint的扫描模式分为快速模式和精准模式,快速模式只检查常用端口对应的服务,所以速度较快,精准模式不在乎性能,只求精准,会将所有插件都运行一遍。

End

fingerprintx readme后面还提到了zgrab2,也是类似的用Go编写的服务指纹识别工具,他和zmap是同一个项目组,后面再看看它的源码。

撰写

布局切换

调整 VitePress 的布局样式,以适配不同的阅读习惯和屏幕环境。

全部展开
使侧边栏和内容区域占据整个屏幕的全部宽度。
全部展开,但侧边栏宽度可调
侧边栏宽度可调,但内容区域宽度不变,调整后的侧边栏将可以占据整个屏幕的最大宽度。
全部展开,且侧边栏和内容区域宽度均可调
侧边栏宽度可调,但内容区域宽度不变,调整后的侧边栏将可以占据整个屏幕的最大宽度。
原始宽度
原始的 VitePress 默认布局宽度

页面最大宽度

调整 VitePress 布局中页面的宽度,以适配不同的阅读习惯和屏幕环境。

调整页面最大宽度
一个可调整的滑块,用于选择和自定义页面最大宽度。

内容最大宽度

调整 VitePress 布局中内容区域的宽度,以适配不同的阅读习惯和屏幕环境。

调整内容最大宽度
一个可调整的滑块,用于选择和自定义内容最大宽度。

聚光灯

支持在正文中高亮当前鼠标悬停的行和元素,以优化阅读和专注困难的用户的阅读体验。

ON开启
开启聚光灯。
OFF关闭
关闭聚光灯。

聚光灯样式

调整聚光灯的样式。

置于底部
在当前鼠标悬停的元素下方添加一个纯色背景以突出显示当前鼠标悬停的位置。
置于侧边
在当前鼠标悬停的元素旁边添加一条固定的纯色线以突出显示当前鼠标悬停的位置。